Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f055d3ef | ||
|
|
2bacb296ba | ||
|
|
8b061f7aa9 | ||
|
|
6a84c83644 | ||
|
|
3227cd0934 | ||
|
|
089feae26f | ||
|
|
99c72d1e10 | ||
|
|
1e67afbe6f | ||
|
|
1cfec1ca45 | ||
|
|
09d1ac9ba3 | ||
|
|
834b9cf384 | ||
|
|
37291b347a | ||
|
|
efdfbeb59e | ||
|
|
a165f9b253 | ||
|
|
4a6fca98bc | ||
|
|
35464128a0 | ||
|
|
398f8a8a2a | ||
|
|
812a1784b2 | ||
|
|
8fa9cd0148 | ||
|
|
0e0662cc9a | ||
|
|
ad4e3f5b20 | ||
|
|
4cd058e1fe | ||
|
|
8f30cee4d3 | ||
|
|
d7f7eb2e6a | ||
|
|
a59cf1162f | ||
|
|
7bc80da99e | ||
|
|
2ddc520bb6 | ||
|
|
d92bb0f4d4 | ||
|
|
abcfe57add | ||
|
|
cdae75beef | ||
|
|
e7bcc2224c | ||
|
|
b591c66dd5 | ||
|
|
402bc0e5ed | ||
|
|
ac772531ee | ||
|
|
9a3edb07e4 | ||
|
|
fea45d2398 | ||
|
|
18aa1b2040 | ||
|
|
9afe38d043 | ||
|
|
5ddbecf97e | ||
|
|
7b573ca86e | ||
|
|
a9256fed05 | ||
|
|
8ec10e7a6a |
@@ -1,13 +1,13 @@
|
|||||||
RPG Docs
|
RPG Docs
|
||||||
========
|
========
|
||||||
|
|
||||||
This is the repo for [DiceCloud](dicecloud.com). The currently deployed version should always be the latest release of the master branch.
|
This is the repo for [DiceCloud](dicecloud.com).
|
||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
`git clone https://github.com/ThaumRystra/RPG-Docs RPG-Docs`
|
`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
|
||||||
`cd RPG-Docs`
|
`cd dicecloud`
|
||||||
`cd rpg-docs`
|
`cd rpg-docs`
|
||||||
`bower install`
|
`bower install`
|
||||||
`meteor`
|
`meteor`
|
||||||
|
|||||||
4739
dataSources/srd/spells.json
Normal file
4739
dataSources/srd/spells.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,3 +10,8 @@ _.each(items, (item) => {
|
|||||||
item.library = "SRDLibraryGA3XWsd"
|
item.library = "SRDLibraryGA3XWsd"
|
||||||
LibraryItems.insert(item)
|
LibraryItems.insert(item)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_.each(spells, (spell) => {
|
||||||
|
spell.library = "SRDLibraryGA3XWsd"
|
||||||
|
LibrarySpells.insert(spell)
|
||||||
|
});
|
||||||
|
|||||||
@@ -47,3 +47,5 @@ ecmascript@0.6.1
|
|||||||
es5-shim@4.6.15
|
es5-shim@4.6.15
|
||||||
differential:vulcanize
|
differential:vulcanize
|
||||||
reactive-dict
|
reactive-dict
|
||||||
|
percolate:synced-cron
|
||||||
|
ongoworks:speakingurl
|
||||||
|
|||||||
@@ -85,8 +85,10 @@ npm-mongo@2.2.16_1
|
|||||||
oauth@1.1.12
|
oauth@1.1.12
|
||||||
oauth2@1.1.11
|
oauth2@1.1.11
|
||||||
observe-sequence@1.0.14
|
observe-sequence@1.0.14
|
||||||
|
ongoworks:speakingurl@9.0.0
|
||||||
ordered-dict@1.0.9
|
ordered-dict@1.0.9
|
||||||
percolate:migrations@0.9.8
|
percolate:migrations@0.9.8
|
||||||
|
percolate:synced-cron@1.3.2
|
||||||
promise@0.8.8
|
promise@0.8.8
|
||||||
raix:eventemitter@0.1.3
|
raix:eventemitter@0.1.3
|
||||||
random@1.0.10
|
random@1.0.10
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ Schemas.Action = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Schemas.Attack = new SimpleSchema({
|
|||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "New Attack",
|
defaultValue: "New Attack",
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Schemas.Buff = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Characters = new Mongo.Collection("characters");
|
|||||||
Schemas.Character = new SimpleSchema({
|
Schemas.Character = new SimpleSchema({
|
||||||
//strings
|
//strings
|
||||||
name: {type: String, defaultValue: "", trim: false, optional: true},
|
name: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
|
urlName: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
alignment: {type: String, defaultValue: "", trim: false, optional: true},
|
alignment: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
gender: {type: String, defaultValue: "", trim: false, optional: true},
|
gender: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
race: {type: String, defaultValue: "", trim: false, optional: true},
|
race: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
@@ -184,6 +185,9 @@ Schemas.Character = new SimpleSchema({
|
|||||||
defaultValue: "whitelist",
|
defaultValue: "whitelist",
|
||||||
allowedValues: ["whitelist", "public"],
|
allowedValues: ["whitelist", "public"],
|
||||||
},
|
},
|
||||||
|
"settings.exportFeatures": {type: Boolean, defaultValue: true},
|
||||||
|
"settings.exportAttacks": {type: Boolean, defaultValue: true},
|
||||||
|
"settings.exportDescription": {type: Boolean, defaultValue: true},
|
||||||
});
|
});
|
||||||
|
|
||||||
Characters.attachSchema(Schemas.Character);
|
Characters.attachSchema(Schemas.Character);
|
||||||
@@ -254,7 +258,10 @@ var attributeBase = preventLoop(function(charId, statName){
|
|||||||
var result = (base + add) * mul;
|
var result = (base + add) * mul;
|
||||||
if (result < min) result = min;
|
if (result < min) result = min;
|
||||||
if (result > max) result = max;
|
if (result > max) result = max;
|
||||||
|
// Don't round carry multiplier
|
||||||
|
if (statName === "carryMultiplier"){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
return Math.floor(result);
|
return Math.floor(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -534,6 +541,12 @@ if (Meteor.isServer){
|
|||||||
Items .remove({charId: character._id});
|
Items .remove({charId: character._id});
|
||||||
Containers .remove({charId: character._id});
|
Containers .remove({charId: character._id});
|
||||||
});
|
});
|
||||||
|
Characters.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||||
|
if (_.contains(fieldNames, "name")){
|
||||||
|
var urlName = getSlug(doc.name, {maintainCase: true});
|
||||||
|
Characters.update(doc._id, {$set: {urlName}});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Characters.allow({
|
Characters.allow({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Classes = new Mongo.Collection("classes");
|
|||||||
|
|
||||||
Schemas.Class = new SimpleSchema({
|
Schemas.Class = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
level: {type: Number},
|
level: {type: Number},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: Date,
|
type: Date,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Experiences = new Mongo.Collection("experience");
|
|||||||
|
|
||||||
Schemas.Experience = new SimpleSchema({
|
Schemas.Experience = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, defaultValue: "New Experience", trim: false},
|
name: {type: String, optional: true, trim: false, defaultValue: "New Experience"},
|
||||||
description: {type: String, optional: true, trim: false},
|
description: {type: String, optional: true, trim: false},
|
||||||
value: {type: Number, defaultValue: 0},
|
value: {type: Number, defaultValue: 0},
|
||||||
dateAdded: {
|
dateAdded: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Features = new Mongo.Collection("features");
|
|||||||
|
|
||||||
Schemas.Feature = new SimpleSchema({
|
Schemas.Feature = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
description: {type: String, optional: true, trim: false},
|
description: {type: String, optional: true, trim: false},
|
||||||
uses: {type: String, optional: true, trim: false},
|
uses: {type: String, optional: true, trim: false},
|
||||||
used: {type: Number, defaultValue: 0},
|
used: {type: Number, defaultValue: 0},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Notes = new Mongo.Collection("notes");
|
|||||||
|
|
||||||
Schemas.Note = new SimpleSchema({
|
Schemas.Note = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
description: {type: String, optional: true, trim: false},
|
description: {type: String, optional: true, trim: false},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ SpellLists = new Mongo.Collection("spellLists");
|
|||||||
|
|
||||||
Schemas.SpellLists = new SimpleSchema({
|
Schemas.SpellLists = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
description: {type: String, optional: true, trim: false},
|
description: {type: String, optional: true, trim: false},
|
||||||
saveDC: {type: String, optional: true, trim: false},
|
saveDC: {type: String, optional: true, trim: false},
|
||||||
attackBonus: {type: String, optional: true, trim: false},
|
attackBonus: {type: String, optional: true, trim: false},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Schemas.Spell = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
defaultValue: "New Spell",
|
defaultValue: "New Spell",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Containers = new Mongo.Collection("containers");
|
Containers = new Mongo.Collection("containers");
|
||||||
|
|
||||||
Schemas.Container = new SimpleSchema({
|
Schemas.Container = new SimpleSchema({
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
isCarried: {type: Boolean},
|
isCarried: {type: Boolean},
|
||||||
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Items = new Mongo.Collection("items");
|
Items = new Mongo.Collection("items");
|
||||||
|
|
||||||
Schemas.Item = new SimpleSchema({
|
Schemas.Item = new SimpleSchema({
|
||||||
name: {type: String, defaultValue: "New Item", trim: false},
|
name: {type: String, optional: true, trim: false, defaultValue: "New Item"},
|
||||||
plural: {type: String, optional: true, trim: false},
|
plural: {type: String, optional: true, trim: false},
|
||||||
description:{type: String, optional: true, trim: false},
|
description:{type: String, optional: true, trim: false},
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
|
||||||
|
|||||||
66
rpg-docs/Model/Library/LibrarySpells.js
Normal file
66
rpg-docs/Model/Library/LibrarySpells.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
LibrarySpells = new Mongo.Collection("librarySpells");
|
||||||
|
|
||||||
|
Schemas.LibrarySpells = new SimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
trim: false,
|
||||||
|
defaultValue: "New Spell",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
trim: false,
|
||||||
|
},
|
||||||
|
castingTime: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: "action",
|
||||||
|
trim: false,
|
||||||
|
},
|
||||||
|
range: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
trim: false,
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
trim: false,
|
||||||
|
defaultValue: "Instantaneous",
|
||||||
|
},
|
||||||
|
"components.verbal": {type: Boolean, defaultValue: false},
|
||||||
|
"components.somatic": {type: Boolean, defaultValue: false},
|
||||||
|
"components.concentration": {type: Boolean, defaultValue: false},
|
||||||
|
"components.material": {type: String, optional: true},
|
||||||
|
ritual: {
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
defaultValue: 1,
|
||||||
|
},
|
||||||
|
school: {
|
||||||
|
type: String,
|
||||||
|
defaultValue: "Abjuration",
|
||||||
|
allowedValues: magicSchools,
|
||||||
|
},
|
||||||
|
library: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
|
effects: {type: [Schemas.LibraryEffects], defaultValue: []},
|
||||||
|
attacks: {type: [Schemas.LibraryAttacks], defaultValue: []},
|
||||||
|
});
|
||||||
|
|
||||||
|
LibrarySpells.attachSchema(Schemas.LibrarySpells);
|
||||||
|
|
||||||
|
LibrarySpells.allow({
|
||||||
|
insert(userId, doc) {
|
||||||
|
return Libraries.canEdit(userId, doc.library);
|
||||||
|
},
|
||||||
|
update(userId, doc, fields, modifier) {
|
||||||
|
return Libraries.canEdit(userId, doc.library);
|
||||||
|
},
|
||||||
|
remove(userId, doc) {
|
||||||
|
return Libraries.canEdit(userId, doc.library);
|
||||||
|
},
|
||||||
|
fetch: ["library"],
|
||||||
|
});
|
||||||
@@ -24,7 +24,7 @@ Router.map(function() {
|
|||||||
this.route("characterList", {
|
this.route("characterList", {
|
||||||
path: "/characterList",
|
path: "/characterList",
|
||||||
waitOn: function(){
|
waitOn: function(){
|
||||||
return subsManager.subscribe("characterList", Meteor.userId());
|
return subsManager.subscribe("characterList");
|
||||||
},
|
},
|
||||||
onAfterAction: function() {
|
onAfterAction: function() {
|
||||||
document.title = appName + " - Characters";
|
document.title = appName + " - Characters";
|
||||||
@@ -32,11 +32,27 @@ Router.map(function() {
|
|||||||
fastRender: true,
|
fastRender: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route("characterSheet", {
|
this.route("characterSheetNaked", {
|
||||||
path: "/character/:_id",
|
path: "/character/:_id/",
|
||||||
waitOn: function(){
|
waitOn: function(){
|
||||||
return [
|
return [
|
||||||
subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()),
|
subsManager.subscribe("singleCharacter", this.params._id),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
action: function(){
|
||||||
|
var _id = this.params._id
|
||||||
|
var character = Characters.findOne(_id);
|
||||||
|
var urlName = character && character.urlName;
|
||||||
|
var path = `\/character\/${_id}\/${urlName}`;
|
||||||
|
Router.go(path,{},{replaceState:true});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route("characterSheet", {
|
||||||
|
path: "/character/:_id/:urlName",
|
||||||
|
waitOn: function(){
|
||||||
|
return [
|
||||||
|
subsManager.subscribe("singleCharacter", this.params._id),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ Template.registerHelper("evaluateString", function(charId, string) {
|
|||||||
return evaluateString(charId, string);
|
return evaluateString(charId, string);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
|
||||||
|
return evaluateSpellString(charId, spellListId, string);
|
||||||
|
});
|
||||||
|
|
||||||
Template.registerHelper("evaluateShortString", function(charId, string) {
|
Template.registerHelper("evaluateShortString", function(charId, string) {
|
||||||
if (_.isString(string)){
|
if (_.isString(string)){
|
||||||
return evaluateString(
|
return evaluateString(
|
||||||
|
|||||||
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
improvedInitiativeJson = function(charId, options){
|
||||||
|
options = options || {
|
||||||
|
features: true,
|
||||||
|
attacks: true,
|
||||||
|
description: true,
|
||||||
|
};
|
||||||
|
var char = Characters.findOne(charId);
|
||||||
|
if (!char) return;
|
||||||
|
var baseValue = function(attributeName){
|
||||||
|
return Characters.calculate.attributeBase(charId, attributeName);
|
||||||
|
};
|
||||||
|
var skillMod = function(skillName){
|
||||||
|
return Characters.calculate.skillMod(charId, skillName);
|
||||||
|
};
|
||||||
|
var damageMods = getDamageMods(charId);
|
||||||
|
return JSON.stringify({
|
||||||
|
"Id": char._id,
|
||||||
|
"Name": char.name,
|
||||||
|
"Source": "DiceCloud",
|
||||||
|
"Type": char.race,
|
||||||
|
"HP": {
|
||||||
|
"Value": baseValue("hitPoints"),
|
||||||
|
"Notes": getHitDiceString(charId) || "",
|
||||||
|
},
|
||||||
|
"AC": {
|
||||||
|
"Value": baseValue("armor"),
|
||||||
|
"Notes": getArmorString(charId) || "",
|
||||||
|
},
|
||||||
|
"InitiativeModifier": skillMod("initiative"),
|
||||||
|
"Speed": ["" + baseValue("speed")],
|
||||||
|
"Abilities": {
|
||||||
|
"Str": baseValue("strength"),
|
||||||
|
"Dex": baseValue("dexterity"),
|
||||||
|
"Con": baseValue("constitution"),
|
||||||
|
"Cha": baseValue("charisma"),
|
||||||
|
"Int": baseValue("intelligence"),
|
||||||
|
"Wis": baseValue("wisdom"),
|
||||||
|
},
|
||||||
|
"DamageVulnerabilities": damageMods.vulnerabilities,
|
||||||
|
"DamageResistances": damageMods.resistances,
|
||||||
|
"DamageImmunities": damageMods.immunities,
|
||||||
|
"ConditionImmunities": [],
|
||||||
|
"Saves": [
|
||||||
|
{"Name": "Str", "Modifier": skillMod("strengthSave")},
|
||||||
|
{"Name": "Dex", "Modifier": skillMod("dexteritySave")},
|
||||||
|
{"Name": "Con", "Modifier": skillMod("constitutionSave")},
|
||||||
|
{"Name": "Int", "Modifier": skillMod("intelligenceSave")},
|
||||||
|
{"Name": "Wis", "Modifier": skillMod("wisdomSave")},
|
||||||
|
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
|
||||||
|
],
|
||||||
|
"Skills": getSkills(charId),
|
||||||
|
"Senses": [
|
||||||
|
"passive Perception " +
|
||||||
|
Characters.calculate.passiveSkill(charId, "perception")
|
||||||
|
],
|
||||||
|
"Languages": getLanguages(charId),
|
||||||
|
"Challenge": "",
|
||||||
|
"Traits": options.features ? getTraits(charId) : [],
|
||||||
|
"Actions": options.attacks ? getActions(charId) : [],
|
||||||
|
"Reactions": [],
|
||||||
|
"LegendaryActions": [],
|
||||||
|
"Description": options.description ? char.description : "",
|
||||||
|
"Player": Meteor.user().username,
|
||||||
|
}, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var getHitDiceString = function(charId){
|
||||||
|
var d6 = Characters.calculate.attributeBase(charId, "d6HitDice");
|
||||||
|
var d8 = Characters.calculate.attributeBase(charId, "d8HitDice");
|
||||||
|
var d10 = Characters.calculate.attributeBase(charId, "d10HitDice");
|
||||||
|
var d12 = Characters.calculate.attributeBase(charId, "d12HitDice");
|
||||||
|
var con = Characters.calculate.abilityMod(charId,"constitution");
|
||||||
|
var string = "" +
|
||||||
|
(d6 ? `${d6}d6 + ` : "") +
|
||||||
|
(d8 ? `${d8}d8 + ` : "") +
|
||||||
|
(d10 ? `${d10}d10 + ` : "") +
|
||||||
|
(d12 ? `${d12}d12 + ` : "") +
|
||||||
|
con;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getArmorString = function(charId){
|
||||||
|
var bases = Effects.find({
|
||||||
|
charId: charId,
|
||||||
|
stat: "armor",
|
||||||
|
operation: "base",
|
||||||
|
enabled: true,
|
||||||
|
}).map(e => ({
|
||||||
|
ame: e.name,
|
||||||
|
value: evaluateEffect(charId, e),
|
||||||
|
}));
|
||||||
|
var base = bases.length && _.max(bases, b => b.value).name || "";
|
||||||
|
var effects = Effects.find({
|
||||||
|
charId: charId,
|
||||||
|
stat: "armor",
|
||||||
|
operation: {$ne: "base"},
|
||||||
|
enabled: true,
|
||||||
|
}).map(e => e.name);
|
||||||
|
var strings = base ? [base] : [];
|
||||||
|
strings = strings.concat(effects);
|
||||||
|
return strings.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
var getDamageMods = function(charId){
|
||||||
|
// jscs:disable maximumLineLength
|
||||||
|
var multipliers = [
|
||||||
|
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
|
||||||
|
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
|
||||||
|
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
|
||||||
|
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
|
||||||
|
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
|
||||||
|
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
|
||||||
|
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
|
||||||
|
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
|
||||||
|
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
|
||||||
|
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
|
||||||
|
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
|
||||||
|
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
|
||||||
|
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
|
||||||
|
];
|
||||||
|
// jscs:enable maximumLineLength
|
||||||
|
multipliers = _.groupBy(multipliers, "value");
|
||||||
|
var names = o => o.name;
|
||||||
|
return {
|
||||||
|
"immunities": _.map(multipliers["0"], names),
|
||||||
|
"resistances": _.map(multipliers["0.5"], names),
|
||||||
|
"vulnerabilities": _.map(multipliers["2"], names),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var getSkills = function(charId){
|
||||||
|
var allSkills = [
|
||||||
|
{name: "acrobatics", attribute: "dexterity"},
|
||||||
|
{name: "animalHandling", attribute: "wisdom"},
|
||||||
|
{name: "arcana", attribute: "intelligence"},
|
||||||
|
{name: "athletics", attribute: "strength"},
|
||||||
|
{name: "deception", attribute: "charisma"},
|
||||||
|
{name: "history", attribute: "intelligence"},
|
||||||
|
{name: "insight", attribute: "wisdom"},
|
||||||
|
{name: "intimidation", attribute: "charisma"},
|
||||||
|
{name: "investigation", attribute: "intelligence"},
|
||||||
|
{name: "medicine", attribute: "wisdom"},
|
||||||
|
{name: "nature", attribute: "intelligence"},
|
||||||
|
{name: "perception", attribute: "wisdom"},
|
||||||
|
{name: "performance", attribute: "charisma"},
|
||||||
|
{name: "persuasion", attribute: "charisma"},
|
||||||
|
{name: "religion", attribute: "intelligence"},
|
||||||
|
{name: "sleightOfHand", attribute: "dexterity"},
|
||||||
|
{name: "stealth", attribute: "dexterity"},
|
||||||
|
{name: "survival", attribute: "wisdom"},
|
||||||
|
];
|
||||||
|
var skills = [];
|
||||||
|
_.each(allSkills, skill => {
|
||||||
|
var value = Characters.calculate.skillMod(charId, skill.name);
|
||||||
|
var mod = Characters.calculate.abilityMod(charId, skill.attribute);
|
||||||
|
if (value !== mod){
|
||||||
|
skills.push({Name: skill.name, Modifier: value});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return skills;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getLanguages = function(charId){
|
||||||
|
return Proficiencies.find({
|
||||||
|
charId,
|
||||||
|
enabled: true,
|
||||||
|
type: "language",
|
||||||
|
}).map(l => l.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getTraits = function(charId){
|
||||||
|
return Features.find(
|
||||||
|
{charId: charId},
|
||||||
|
{sort: {color: 1, name: 1}}
|
||||||
|
).map(f => ({
|
||||||
|
Name: f.name,
|
||||||
|
// evaluateShortString helper
|
||||||
|
Content: evaluateString(
|
||||||
|
charId, f.description && f.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
|
||||||
|
) || "",
|
||||||
|
Usage: "",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var getActions = function(charId){
|
||||||
|
return Attacks.find(
|
||||||
|
{charId, enabled: true},
|
||||||
|
{sort: {color: 1, name: 1}}
|
||||||
|
).map(a => ({
|
||||||
|
Name: a.name,
|
||||||
|
Content: `+${evaluate(charId, a.attackBonus)} to hit, ` +
|
||||||
|
`${evaluateString(charId, a.damage)} ${a.damageType} damage, ` +
|
||||||
|
`${a.details}`,
|
||||||
|
Usage: "",
|
||||||
|
}));
|
||||||
|
}
|
||||||
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||||
|
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
|
||||||
|
|
||||||
|
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
|
||||||
|
|
||||||
|
// MIT license
|
||||||
|
var lastTime = 0;
|
||||||
|
var vendors = ["ms", "moz", "webkit", "o"];
|
||||||
|
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||||
|
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"];
|
||||||
|
window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] ||
|
||||||
|
window[vendors[x] + "CancelRequestAnimationFrame"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.requestAnimationFrame)
|
||||||
|
window.requestAnimationFrame = function(callback, element) {
|
||||||
|
var currTime = new Date().getTime();
|
||||||
|
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||||
|
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||||||
|
timeToCall);
|
||||||
|
lastTime = currTime + timeToCall;
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!window.cancelAnimationFrame)
|
||||||
|
window.cancelAnimationFrame = function(id) {
|
||||||
|
clearTimeout(id);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
column-gap: 0px;
|
column-gap: 0px;
|
||||||
column-width: 304px;
|
column-width: 304px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container.thin-columns {
|
.column-container.thin-columns {
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
.card {
|
.card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
position: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .top {
|
.card .top {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
body .paper-font-display4, body .paper-font-display3, body .paper-font-title, body .paper-font-caption{
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.white-text {
|
.white-text {
|
||||||
color: #dedede;
|
color: #dedede;
|
||||||
color: rgba(255,255,255,0.87);
|
color: rgba(255,255,255,0.87);
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
<iron-icon icon="settings" item-icon></iron-icon>
|
<iron-icon icon="settings" item-icon></iron-icon>
|
||||||
Settings
|
Settings
|
||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
|
<paper-icon-item id="characterExport">
|
||||||
|
<iron-icon icon="content-copy" item-icon></iron-icon>
|
||||||
|
Export to Improved Initiative
|
||||||
|
</paper-icon-item>
|
||||||
</paper-menu>
|
</paper-menu>
|
||||||
</paper-menu-button>
|
</paper-menu-button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@@ -203,4 +203,11 @@ Template.characterSheet.events({
|
|||||||
element: event.currentTarget.parentElement.parentElement,
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
"click #characterExport": function(event, instance){
|
||||||
|
pushDialogStack({
|
||||||
|
data: this,
|
||||||
|
template: "exportDialog",
|
||||||
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,3 +13,13 @@
|
|||||||
.effectEdit .deleteEffect {
|
.effectEdit .deleteEffect {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.effectEdit .effect-table-view {
|
||||||
|
align-self: center;
|
||||||
|
color: #757575;
|
||||||
|
color: rgba(0,0,0,0.54);
|
||||||
|
}
|
||||||
|
|
||||||
|
.effectEdit .iron-selected {
|
||||||
|
color: #ad2a1f;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,45 +1,55 @@
|
|||||||
<template name="effectEdit">
|
<template name="effectEdit">
|
||||||
<div class="effectEdit layout horizontal center">
|
{{#with effect}}
|
||||||
<paper-dropdown-menu label="Stat" class="statDropDown" dynamic-align>
|
{{#baseEditDialog hideColor=true title="Effect"}}
|
||||||
<dicecloud-selector class="statMenu dropdown-content" selected={{stat}} selectable="paper-item">
|
<div class="fit layout vertical effectEdit" style="padding: 24px;">
|
||||||
{{#each statGroups}}
|
<table class="paper-font-display1 effect-table-view">
|
||||||
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
<tr>
|
||||||
{{this}}
|
{{> effectView}}
|
||||||
</div>
|
</tr>
|
||||||
{{#each stats}}
|
</table>
|
||||||
<paper-item name={{stat}}>{{name}}</paper-item>
|
<hr class="vertMargin" style="width: 100%;">
|
||||||
{{/each}}
|
{{#if showEffectValueInput}}
|
||||||
{{/each}}
|
<paper-input class="effectValueInput"
|
||||||
</dicecloud-selector>
|
label="Value"
|
||||||
</paper-dropdown-menu>
|
floatinglabel
|
||||||
{{#if operations}}
|
value={{effectValue}}>
|
||||||
<paper-dropdown-menu class="operationDropDown" label="Operation" dynamic-align>
|
</paper-input>
|
||||||
<dicecloud-selector class="dropdown-content operationMenu" selected={{operation}}>
|
{{else}}
|
||||||
{{#each operations}}
|
<div style="height: 62px;"></div>
|
||||||
<paper-item name={{operation}}>{{name}}</paper-item>
|
{{/if}}
|
||||||
|
<div class="effectEdit layout horizontal flex">
|
||||||
|
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
|
||||||
|
{{#each statGroups}}
|
||||||
|
<div class="statGroupTitle clickable" style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
||||||
|
{{this}}
|
||||||
|
</div>
|
||||||
|
<iron-collapse opened={{isGroupSelected this ../stat}}>
|
||||||
|
{{#each stats}}
|
||||||
|
<paper-item name={{stat}} class="clickable">{{name}}</paper-item>
|
||||||
|
{{/each}}
|
||||||
|
</iron-collapse>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</dicecloud-selector>
|
</dicecloud-selector>
|
||||||
</paper-dropdown-menu>
|
{{#if operations}}
|
||||||
{{/if}}
|
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
|
||||||
{{#if effectValueTemplate}}
|
{{#each operations}}
|
||||||
{{> Template.dynamic template=effectValueTemplate}}
|
<paper-item name={{operation}} class="clickable">{{name}}</paper-item>
|
||||||
{{else}}
|
{{/each}}
|
||||||
<div class="flex"></div>
|
</dicecloud-selector>
|
||||||
{{/if}}
|
{{else}} {{#if showMultiplierOperations}}
|
||||||
<paper-icon-button class="deleteEffect"
|
<dicecloud-selector class="multiplierMenu flex"
|
||||||
icon="delete">
|
selected={{value}}>
|
||||||
</paper-icon-button>
|
<paper-item name="0.5" class="clickable">Resistance</paper-item>
|
||||||
<br>
|
<paper-item name="2" class="clickable">Vulnerability</paper-item>
|
||||||
</div>
|
<paper-item name="0" class="clickable">Immunity</paper-item>
|
||||||
</template>
|
</dicecloud-selector>
|
||||||
|
{{else}}
|
||||||
<template name="regularEffectValue">
|
<div class="flex" style="height: 100%;"></div>
|
||||||
<paper-input class="effectValueInput flex"
|
{{/if}} {{/if}}
|
||||||
label="Value"
|
</div>
|
||||||
floatinglabel
|
</div>
|
||||||
value={{effectValue}}
|
{{/baseEditDialog}}
|
||||||
style="flex-basis: 100px;">
|
{{/with}}
|
||||||
</paper-input>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="multiplierEffectValue">
|
<template name="multiplierEffectValue">
|
||||||
@@ -51,5 +61,4 @@
|
|||||||
<paper-item name="0">Immunity</paper-item>
|
<paper-item name="0">Immunity</paper-item>
|
||||||
</dicecloud-selector>
|
</dicecloud-selector>
|
||||||
</paper-dropdown-menu>
|
</paper-dropdown-menu>
|
||||||
<div class="flex"></div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -97,7 +97,19 @@ var skillOperations = [
|
|||||||
{name: "Conditional Benefit", operation: "conditional"},
|
{name: "Conditional Benefit", operation: "conditional"},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Template.effectEdit.onRendered(function(){
|
||||||
|
_.defer(() => {
|
||||||
|
const statElement = this.find(".statMenu .iron-selected");
|
||||||
|
statElement && statElement.scrollIntoView();
|
||||||
|
const opElement = this.find(".operationMenu .iron-selected");
|
||||||
|
opElement && opElement.scrollIntoView();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Template.effectEdit.helpers({
|
Template.effectEdit.helpers({
|
||||||
|
effect: function(){
|
||||||
|
return Effects.findOne(this.id);
|
||||||
|
},
|
||||||
statGroups: function(){
|
statGroups: function(){
|
||||||
return statGroupNames;
|
return statGroupNames;
|
||||||
},
|
},
|
||||||
@@ -115,46 +127,101 @@ Template.effectEdit.helpers({
|
|||||||
return attributeOperations;
|
return attributeOperations;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
effectValueTemplate: function(){
|
showMultiplierOperations: function(){
|
||||||
//resistance/vulnerability template
|
var stat = statsDict[this.stat];
|
||||||
|
return stat && stat.group === "Weakness/Resistance"
|
||||||
|
},
|
||||||
|
showEffectValueInput: function(){
|
||||||
var stat = statsDict[this.stat];
|
var stat = statsDict[this.stat];
|
||||||
var group = stat && stat.group;
|
var group = stat && stat.group;
|
||||||
if (group === "Weakness/Resistance") return "multiplierEffectValue";
|
if (
|
||||||
|
group === "Weakness/Resistance"
|
||||||
|
) return false;
|
||||||
var op = this.operation;
|
var op = this.operation;
|
||||||
if (!op) return null;
|
if (
|
||||||
//operations that don't need templates
|
!op ||
|
||||||
if (op === "advantage" || op === "disadvantage" || op === "fail") return null;
|
op === "advantage" ||
|
||||||
|
op === "disadvantage" ||
|
||||||
//default template
|
op === "fail"
|
||||||
return "regularEffectValue";
|
) return false;
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
Template.regularEffectValue.helpers({
|
|
||||||
effectValue: function(){
|
effectValue: function(){
|
||||||
return this.calculation || this.value;
|
return this.calculation || this.value;
|
||||||
}
|
},
|
||||||
|
isGroupSelected: function(groupName, statName){
|
||||||
|
var stat = statsDict[statName]
|
||||||
|
return stat && (stat.group === groupName);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var setStat = function(statName, effectId){
|
||||||
|
var setter = {stat: statName};
|
||||||
|
var effect = Effects.findOne(this._id);
|
||||||
|
var group = statsDict[statName].group;
|
||||||
|
if (group === "Saving Throws" || group === "Skills"){
|
||||||
|
// Skills must have a valid skill operation
|
||||||
|
if (!_.contains(
|
||||||
|
_.map(skillOperations, ao => ao.operation),
|
||||||
|
effect.operation
|
||||||
|
)){
|
||||||
|
setter.operation = "add";
|
||||||
|
}
|
||||||
|
} else if (group !== "Weakness/Resistance"){
|
||||||
|
// Attributes must have a valid attribute operation
|
||||||
|
if (!_.contains(
|
||||||
|
_.map(attributeOperations, ao => ao.operation),
|
||||||
|
effect.operation
|
||||||
|
)){
|
||||||
|
setter.operation = "base";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Weakness/Resistance must have a mul operation and value
|
||||||
|
if (effect.operation !== "mul"){
|
||||||
|
setter.operation = "mul";
|
||||||
|
}
|
||||||
|
if (!_.contains([0, 0.5, 2], effect.value)){
|
||||||
|
setter.value = 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Effects.update(effectId, {$set: setter});
|
||||||
|
};
|
||||||
|
|
||||||
|
var scrollAnimationId;
|
||||||
|
var scrollElementToView = element => {
|
||||||
|
var scrollFunction = function(){
|
||||||
|
element.scrollIntoView();
|
||||||
|
scrollAnimationId = requestAnimationFrame(scrollFunction);
|
||||||
|
};
|
||||||
|
return scrollFunction;
|
||||||
|
}
|
||||||
|
|
||||||
Template.effectEdit.events({
|
Template.effectEdit.events({
|
||||||
"click .deleteEffect": function(event){
|
"click #deleteButton": function(event, instance){
|
||||||
Effects.softRemoveNode(this._id);
|
Effects.softRemoveNode(instance.data.id);
|
||||||
GlobalUI.deletedToast(this._id, "Effects", "Effect");
|
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
|
||||||
|
popDialogStack();
|
||||||
},
|
},
|
||||||
"iron-select .statDropDown": function(event){
|
"click .statGroupTitle": function(event, instance){
|
||||||
|
var groupName = this.toString();
|
||||||
|
var firstStat = statGroups[groupName][0].stat;
|
||||||
|
setStat(firstStat, instance.data.id);
|
||||||
|
scrollAnimationId = requestAnimationFrame(scrollElementToView(event.target));
|
||||||
|
_.delay(() => cancelAnimationFrame(scrollAnimationId), 300);
|
||||||
|
},
|
||||||
|
"iron-select .statMenu": function(event){
|
||||||
var detail = event.originalEvent.detail;
|
var detail = event.originalEvent.detail;
|
||||||
var statName = detail.item.getAttribute("name");
|
var statName = detail.item.getAttribute("name");
|
||||||
if (statName == this.stat) return;
|
if (statName == this.stat) return;
|
||||||
Effects.update(this._id, {$set: {stat: statName}});
|
setStat(statName, this._id);
|
||||||
},
|
},
|
||||||
"iron-select .operationDropDown": function(event){
|
"iron-select .operationMenu": function(event){
|
||||||
var detail = event.originalEvent.detail;
|
var detail = event.originalEvent.detail;
|
||||||
var opName = detail.item.getAttribute("name");
|
var opName = detail.item.getAttribute("name");
|
||||||
if (opName == this.operation) return;
|
if (opName == this.operation) return;
|
||||||
Effects.update(this._id, {$set: {operation: opName}});
|
Effects.update(this._id, {$set: {operation: opName}});
|
||||||
},
|
},
|
||||||
"iron-select .damageMultiplierDropDown": function(event){
|
"iron-select .multiplierMenu": function(event){
|
||||||
var detail = event.originalEvent.detail;
|
var detail = event.originalEvent.detail;
|
||||||
var value = +detail.item.getAttribute("name");
|
var value = +detail.item.getAttribute("name");
|
||||||
if (value == this.value) return;
|
if (value == this.value) return;
|
||||||
@@ -164,15 +231,25 @@ Template.effectEdit.events({
|
|||||||
operation: "mul",
|
operation: "mul",
|
||||||
}});
|
}});
|
||||||
},
|
},
|
||||||
"change .effectValueInput": function(event){
|
"change .effectValueInput, input .effectValueInput":
|
||||||
|
_.debounce(function(event){
|
||||||
var value = event.currentTarget.value;
|
var value = event.currentTarget.value;
|
||||||
var numValue = +value;
|
var numValue = value === "" ? NaN : +value;
|
||||||
if (_.isFinite(numValue)){
|
if (_.isFinite(numValue)){
|
||||||
if (this.value === numValue) return;
|
if (this.value === numValue) return;
|
||||||
Effects.update(this._id, {$set: {value: numValue, calculation: ""}});
|
Effects.update(this._id, {
|
||||||
|
$set: {value: numValue},
|
||||||
|
$unset: {calculation: ""},
|
||||||
|
});
|
||||||
} else if (_.isString(value)){
|
} else if (_.isString(value)){
|
||||||
if (this.calculation === value) return;
|
if (this.calculation === value) return;
|
||||||
Effects.update(this._id, {$set: {value: "", calculation: value}});
|
Effects.update(this._id, {
|
||||||
|
$set: {calculation: value},
|
||||||
|
$unset: {value: ""},
|
||||||
|
}, {
|
||||||
|
removeEmptyStrings: false,
|
||||||
|
trimStrings: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
}, 400),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<template name="effectView">
|
<template name="effectView">
|
||||||
<tr>
|
<td>{{statName}}</td>
|
||||||
<td>{{statName}}</td>
|
<td>{{operationName}}{{statValue}}</td>
|
||||||
<td>{{operationName}}{{statValue}}</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//TODO add dexterity armor
|
|
||||||
var stats = {
|
var stats = {
|
||||||
"strength":{"name":"Strength"},
|
"strength":{"name":"Strength"},
|
||||||
"dexterity":{"name":"Dexterity"},
|
"dexterity":{"name":"Dexterity"},
|
||||||
@@ -131,8 +130,10 @@ Template.effectView.helpers({
|
|||||||
return stats[this.stat] && stats[this.stat].name || "No Stat";
|
return stats[this.stat] && stats[this.stat].name || "No Stat";
|
||||||
},
|
},
|
||||||
operationName: function(){
|
operationName: function(){
|
||||||
if (this.operation === "proficiency" ||
|
if (
|
||||||
this.operation === "conditional") return null;
|
this.operation === "proficiency" ||
|
||||||
|
this.operation === "conditional"
|
||||||
|
) return null;
|
||||||
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
|
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
|
||||||
return null;
|
return null;
|
||||||
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
|
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
|
||||||
@@ -141,9 +142,11 @@ Template.effectView.helpers({
|
|||||||
operations[this.operation].name || "No Operation";
|
operations[this.operation].name || "No Operation";
|
||||||
},
|
},
|
||||||
statValue: function(){
|
statValue: function(){
|
||||||
if (this.operation === "advantage" ||
|
if (
|
||||||
this.operation === "disadvantage" ||
|
this.operation === "advantage" ||
|
||||||
this.operation === "fail"){
|
this.operation === "disadvantage" ||
|
||||||
|
this.operation === "fail"
|
||||||
|
){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (this.operation === "proficiency"){
|
if (this.operation === "proficiency"){
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.effectsEditList .effect {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
<!--needs to be given charId, parentId and parentCollection-->
|
<!--needs to be given charId, parentId and parentCollection-->
|
||||||
<template name="effectsEditList">
|
<template name="effectsEditList">
|
||||||
{{#if effects.count}}
|
{{#if effects.length}}
|
||||||
<div id="effects">
|
<div class="effectsEditList">
|
||||||
<div class="paper-font-title">Effects</div>
|
<div class="paper-font-title">Effects</div>
|
||||||
{{#each effects}}
|
<table class="wideTable" style="width: 100%;">
|
||||||
{{>effectEdit}}
|
{{#each effects}}
|
||||||
{{/each}}
|
<tr class="effect" data-id={{_id}}>
|
||||||
|
{{>effectView}}
|
||||||
|
<td>
|
||||||
|
<paper-icon-button class="edit-effect" icon="create">
|
||||||
|
</paper-icon-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button>
|
<paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button>
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ Template.effectsEditList.helpers({
|
|||||||
if (this.parentGroup){
|
if (this.parentGroup){
|
||||||
selector["parent.group"] = this.parentGroup;
|
selector["parent.group"] = this.parentGroup;
|
||||||
}
|
}
|
||||||
var cursor = Effects.find(selector);
|
var effects = Effects.find(selector).fetch();
|
||||||
return cursor;
|
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.effectsEditList.events({
|
Template.effectsEditList.events({
|
||||||
"tap #addEffectButton": function(){
|
"tap #addEffectButton": function(event, instance){
|
||||||
if (!_.isBoolean(this.enabled)) {
|
if (!_.isBoolean(this.enabled)) {
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
}
|
}
|
||||||
Effects.insert({
|
const effectId = Effects.insert({
|
||||||
name: this.name,
|
name: this.name,
|
||||||
charId: this.charId,
|
charId: this.charId,
|
||||||
parent: {
|
parent: {
|
||||||
@@ -29,5 +29,18 @@ Template.effectsEditList.events({
|
|||||||
operation: "add",
|
operation: "add",
|
||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
});
|
});
|
||||||
|
pushDialogStack({
|
||||||
|
template: "effectEdit",
|
||||||
|
data: {id: effectId},
|
||||||
|
element: event.currentTarget,
|
||||||
|
returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"tap .edit-effect": function(event, template){
|
||||||
|
pushDialogStack({
|
||||||
|
template: "effectEdit",
|
||||||
|
data: {id: this._id},
|
||||||
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!--needs to be given charId, (parentId or stat) and type-->
|
<!--needs to be given charId, (parentId or stat) and type-->
|
||||||
<template name="effectsViewList">
|
<template name="effectsViewList">
|
||||||
{{#if effects.count}}
|
{{#if effects.length}}
|
||||||
<div class="effects">
|
<div class="effects">
|
||||||
<div class="spaceAfter paper-font-title">Effects</div>
|
<div class="spaceAfter paper-font-title">Effects</div>
|
||||||
<table class="wideTable">
|
<table class="wideTable">
|
||||||
{{#each effects}}
|
{{#each effects}}
|
||||||
{{>effectView}}
|
<tr>{{>effectView}}</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ Template.effectsViewList.helpers({
|
|||||||
effects: function(){
|
effects: function(){
|
||||||
var selector = {
|
var selector = {
|
||||||
"parent.id": this.parentId,
|
"parent.id": this.parentId,
|
||||||
"charId": this.charId
|
"charId": this.charId,
|
||||||
};
|
};
|
||||||
if (this.parentGroup){
|
if (this.parentGroup){
|
||||||
selector["parent.group"] = this.parentGroup;
|
selector["parent.group"] = this.parentGroup;
|
||||||
}
|
}
|
||||||
return Effects.find(selector, {fields: {parent: 0}});
|
let effects = Effects.find(selector, {
|
||||||
|
fields: {parent: 0},
|
||||||
|
}).fetch();
|
||||||
|
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.exportDialog .iiexport {
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<template name="exportDialog">
|
||||||
|
<div class="exportDialog fit layout vertical">
|
||||||
|
{{#with character}}
|
||||||
|
<app-header fixed effects="waterfall">
|
||||||
|
<app-toolbar>
|
||||||
|
<div main-title>Export Character to Improved Initiative</div>
|
||||||
|
</app-toolbar>
|
||||||
|
</app-header>
|
||||||
|
<div class="form flex layout vertical">
|
||||||
|
<paper-toggle-button id="exportFeatures" checked={{settings.exportFeatures}}>
|
||||||
|
Features
|
||||||
|
</paper-toggle-button>
|
||||||
|
<paper-toggle-button id="exportAttacks" checked={{settings.exportAttacks}}>
|
||||||
|
Attacks
|
||||||
|
</paper-toggle-button>
|
||||||
|
<paper-toggle-button id="exportDescription" checked={{settings.exportDescription}}>
|
||||||
|
Description
|
||||||
|
</paper-toggle-button>
|
||||||
|
<div class="paper-font-title padded">JSON</div>
|
||||||
|
<textarea class="flex iiexport">{{improvedInitiativeJson}}</textarea>
|
||||||
|
<paper-button id="copyExportButton" class="red-button" raised>
|
||||||
|
<iron-icon icon="content-copy"></iron-icon>
|
||||||
|
Copy to Clipboard
|
||||||
|
</paper-button>
|
||||||
|
</div>
|
||||||
|
<div class="buttons layout horizontal end-justified">
|
||||||
|
<paper-button class="doneButton"> Done </paper-button>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
Template.exportDialog.helpers({
|
||||||
|
character: function(){
|
||||||
|
return Characters.findOne(this._id);
|
||||||
|
},
|
||||||
|
improvedInitiativeJson: function(){
|
||||||
|
var options = {
|
||||||
|
features: this.settings.exportFeatures,
|
||||||
|
attacks: this.settings.exportAttacks,
|
||||||
|
description: this.settings.exportDescription,
|
||||||
|
}
|
||||||
|
return improvedInitiativeJson(this._id, options);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.exportDialog.events({
|
||||||
|
"change #exportFeatures": function(event, template){
|
||||||
|
Characters.update(this._id, {$set: {
|
||||||
|
"settings.exportFeatures": event.target.checked,
|
||||||
|
}});
|
||||||
|
},
|
||||||
|
"change #exportAttacks": function(event, template){
|
||||||
|
Characters.update(this._id, {$set: {
|
||||||
|
"settings.exportAttacks": event.target.checked,
|
||||||
|
}});
|
||||||
|
},
|
||||||
|
"change #exportDescription": function(event, template){
|
||||||
|
Characters.update(this._id, {$set: {
|
||||||
|
"settings.exportDescription": event.target.checked,
|
||||||
|
}});
|
||||||
|
},
|
||||||
|
"click #copyExportButton": function(event, template){
|
||||||
|
var copyTextarea = template.find(".iiexport");
|
||||||
|
copyTextarea.select();
|
||||||
|
var msg;
|
||||||
|
try {
|
||||||
|
var successful = document.execCommand("copy");
|
||||||
|
var msg = successful ? "JSON copied to clipboard" : "Unable to copy JSON";
|
||||||
|
} catch (err) {
|
||||||
|
msg = "Unable to copy JSON";
|
||||||
|
} finally {
|
||||||
|
clearSelection();
|
||||||
|
GlobalUI.toast(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"click .doneButton": function(event, instance){
|
||||||
|
popDialogStack();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var clearSelection = function(){
|
||||||
|
if (window.getSelection) {
|
||||||
|
if (window.getSelection().empty) { // Chrome
|
||||||
|
window.getSelection().empty();
|
||||||
|
} else if (window.getSelection().removeAllRanges) { // Firefox
|
||||||
|
window.getSelection().removeAllRanges();
|
||||||
|
}
|
||||||
|
} else if (document.selection) { // IE?
|
||||||
|
document.selection.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -29,7 +29,6 @@ Template.newCharacterDialog.helpers({
|
|||||||
|
|
||||||
changeFunction = function(field){
|
changeFunction = function(field){
|
||||||
return _.debounce(function(event, instance){
|
return _.debounce(function(event, instance){
|
||||||
console.log({field, event})
|
|
||||||
instance.character[field] = event.currentTarget.value;
|
instance.character[field] = event.currentTarget.value;
|
||||||
instance.schema.clean(instance.character);
|
instance.schema.clean(instance.character);
|
||||||
instance.context.validate(instance.character);
|
instance.context.validate(instance.character);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
|
<div>{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}</div>
|
||||||
{{> attacksViewList charId=charId parentId=_id}}
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,20 @@
|
|||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spell.item > div > div {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell.item > div {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell.item iron-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.spellLevel {
|
.spellLevel {
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
-webkit-transform: translateX(0);
|
-webkit-transform: translateX(0);
|
||||||
|
|||||||
@@ -131,6 +131,15 @@
|
|||||||
</paper-fab>
|
</paper-fab>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<paper-tooltip position="left">
|
||||||
|
Spell library
|
||||||
|
</paper-tooltip>
|
||||||
|
<paper-fab icon="av:library-books"
|
||||||
|
class="librarySpell"
|
||||||
|
mini>
|
||||||
|
</paper-fab>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<paper-tooltip position="left">
|
<paper-tooltip position="left">
|
||||||
New spell
|
New spell
|
||||||
|
|||||||
@@ -211,8 +211,17 @@ Template.spells.events({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
"click .addSpell": function(event, instance){
|
"click .addSpell": function(event, instance){
|
||||||
var charId = this.charId;
|
var charId = this._id;
|
||||||
var listId = SpellLists.findOne({charId: this._id})._id;
|
var list = SpellLists.findOne({charId});
|
||||||
|
var listId = list && list._id
|
||||||
|
if (!listId){
|
||||||
|
listId = SpellLists.insert({
|
||||||
|
name: "New SpellList",
|
||||||
|
charId: charId,
|
||||||
|
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||||
|
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||||
|
});
|
||||||
|
}
|
||||||
var id = Spells.insert({
|
var id = Spells.insert({
|
||||||
name: "New Spell",
|
name: "New Spell",
|
||||||
charId: this._id,
|
charId: this._id,
|
||||||
@@ -229,6 +238,49 @@ Template.spells.events({
|
|||||||
returnElement: () => instance.find(`.spell[data-id='${id}']`),
|
returnElement: () => instance.find(`.spell[data-id='${id}']`),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
"click .librarySpell": function(event, instance){
|
||||||
|
var charId = this._id;
|
||||||
|
var spellId = Random.id();
|
||||||
|
var list = SpellLists.findOne({charId});
|
||||||
|
var listId = list && list._id
|
||||||
|
pushDialogStack({
|
||||||
|
template: "spellLibraryDialog",
|
||||||
|
element: event.currentTarget,
|
||||||
|
callback: (result) => {
|
||||||
|
if (!result) return;
|
||||||
|
if (!listId){
|
||||||
|
listId = SpellLists.insert({
|
||||||
|
name: "New SpellList",
|
||||||
|
charId: charId,
|
||||||
|
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||||
|
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Make the library spell into a regular spell
|
||||||
|
let spell = _.omit(result, "library", "attacks", "effects");
|
||||||
|
spell._id = spellId;
|
||||||
|
spell.charId = charId;
|
||||||
|
spell.parent = {
|
||||||
|
id: listId,
|
||||||
|
collection: "SpellLists",
|
||||||
|
};
|
||||||
|
spell.prepared = "prepared";
|
||||||
|
Spells.insert(spell);
|
||||||
|
// Copy over attacks and effects
|
||||||
|
_.each(result.attacks, (attack) => {
|
||||||
|
attack.charId = charId;
|
||||||
|
attack.parent = {id: spellId, collection: "Spells"};
|
||||||
|
Attacks.insert(attack);
|
||||||
|
});
|
||||||
|
_.each(result.effects, (effect) => {
|
||||||
|
effect.charId = charId;
|
||||||
|
effect.parent = {id: spellId, collection: "Spells"};
|
||||||
|
Effects.insert(effect);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
returnElement: () => $(`[data-id='${spellId}']`).get(0),
|
||||||
|
})
|
||||||
|
},
|
||||||
"click .preparedCheckbox": function(event){
|
"click .preparedCheckbox": function(event){
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.spell-library-dialog .spell.selected {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-library-dialog .category-header {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-library-dialog .category-header iron-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-library-dialog .category-header iron-icon.open {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-library-dialog table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spell-library-dialog .library-spell td {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<template name="spellLibraryDialog">
|
||||||
|
<div class="fit spell-library-dialog layout vertical">
|
||||||
|
<app-toolbar class="app-grey white-text">
|
||||||
|
<paper-icon-button id="backButton"
|
||||||
|
icon="arrow-back">
|
||||||
|
</paper-icon-button>
|
||||||
|
<div main-title>Spells</div>
|
||||||
|
<paper-input label="Search" class="search-input">
|
||||||
|
<iron-icon icon="search" prefix></iron-icon>
|
||||||
|
</paper-input>
|
||||||
|
</app-toolbar>
|
||||||
|
<div class="flex scroll-y">
|
||||||
|
<div class="spells" style="padding:8px">
|
||||||
|
{{#if searchTerm}}
|
||||||
|
{{#if searchSpells.count}}
|
||||||
|
<table style="width: 100%">
|
||||||
|
<tbody>
|
||||||
|
{{#each spell in searchSpells}}
|
||||||
|
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{else}}{{#if searchReady}}
|
||||||
|
No spells match "{{searchTerm}}"
|
||||||
|
{{/if}}{{/if}}
|
||||||
|
{{#unless searchReady}}
|
||||||
|
<div class="layout vertical center" style="width: 100%; padding: 16px;">
|
||||||
|
<paper-spinner active></paper-spinner>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
{{else}}
|
||||||
|
{{#each categories}}
|
||||||
|
<div class="paper-font-body2 category-header clickable">
|
||||||
|
<iron-icon icon="chevron-right" class="{{#if isOpen key}}open{{/if}}">
|
||||||
|
</iron-icon>
|
||||||
|
{{name}}
|
||||||
|
</div>
|
||||||
|
<iron-collapse opened={{isOpen key}}>
|
||||||
|
<table style="width: 100%">
|
||||||
|
<tbody>
|
||||||
|
{{#each spell in (spellsInCategory key)}}
|
||||||
|
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{#unless ready key}}
|
||||||
|
<paper-spinner active></paper-spinner>
|
||||||
|
{{/unless}}
|
||||||
|
</iron-collapse>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layout horizontal end-justified">
|
||||||
|
<paper-button class="cancelButton">Cancel</paper-button>
|
||||||
|
<paper-button class="okButton">OK</paper-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template name="librarySpell">
|
||||||
|
<tr class="spell library-spell {{#if selected}}selected{{/if}}">
|
||||||
|
<td class="spellName">
|
||||||
|
{{spell.name}}
|
||||||
|
<paper-ripple></paper-ripple>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
const librarySubs = new SubsManager();
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{name: "Cantrips", key: 0},
|
||||||
|
{name: "Level 1", key: 1},
|
||||||
|
{name: "Level 2", key: 2},
|
||||||
|
{name: "Level 3", key: 3},
|
||||||
|
{name: "Level 4", key: 4},
|
||||||
|
{name: "Level 5", key: 5},
|
||||||
|
{name: "Level 6", key: 6},
|
||||||
|
{name: "Level 7", key: 7},
|
||||||
|
{name: "Level 8", key: 8},
|
||||||
|
{name: "Level 9", key: 9},
|
||||||
|
];
|
||||||
|
|
||||||
|
Template.spellLibraryDialog.onCreated(function(){
|
||||||
|
this.selectedSpell = new ReactiveVar();
|
||||||
|
this.searchTerm = new ReactiveVar();
|
||||||
|
this.categoriesOpen = new ReactiveVar([]);
|
||||||
|
this.readyDict = new ReactiveDict();
|
||||||
|
this.searchReady = new ReactiveVar();
|
||||||
|
librarySubs.subscribe("standardLibraries");
|
||||||
|
this.autorun(() => {
|
||||||
|
// Subscribe to all open categories
|
||||||
|
_.each(this.categoriesOpen.get(), (key) => {
|
||||||
|
var handle = librarySubs.subscribe("standardLibrarySpells", key);
|
||||||
|
this.autorun(() => {
|
||||||
|
this.readyDict.set(key, handle.ready());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.autorun(() => {
|
||||||
|
// If we are searching, subscibe to all categories
|
||||||
|
if (this.searchTerm.get()){
|
||||||
|
let handles = _.map(categories, category =>
|
||||||
|
librarySubs.subscribe("standardLibrarySpells", category.key)
|
||||||
|
);
|
||||||
|
// Ready when all handles are ready
|
||||||
|
this.autorun(() => {
|
||||||
|
this.searchReady.set(_.every(handles, h => h.ready()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.spellLibraryDialog.helpers({
|
||||||
|
ready(key){
|
||||||
|
return Template.instance().readyDict.get(key);
|
||||||
|
},
|
||||||
|
categories(){
|
||||||
|
return categories;
|
||||||
|
},
|
||||||
|
spellsInCategory(categoryKey){
|
||||||
|
return LibrarySpells.find({
|
||||||
|
library: "SRDLibraryGA3XWsd",
|
||||||
|
level: categoryKey,
|
||||||
|
}, {
|
||||||
|
sort: {name: 1},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isSelected(spell){
|
||||||
|
const selected = Template.instance().selectedSpell.get();
|
||||||
|
return selected && selected._id === spell._id;
|
||||||
|
},
|
||||||
|
isOpen(key){
|
||||||
|
const cats = Template.instance().categoriesOpen.get();
|
||||||
|
return _.contains(cats, key);
|
||||||
|
},
|
||||||
|
searchTerm(){
|
||||||
|
return Template.instance().searchTerm.get();
|
||||||
|
},
|
||||||
|
searchReady(){
|
||||||
|
return Template.instance().searchReady.get();
|
||||||
|
},
|
||||||
|
searchSpells(){
|
||||||
|
const searchTerm = Template.instance().searchTerm.get();
|
||||||
|
if (!searchTerm) return;
|
||||||
|
return LibrarySpells.find({
|
||||||
|
library: "SRDLibraryGA3XWsd",
|
||||||
|
name: {
|
||||||
|
$regex: new RegExp(".*" + searchTerm + ".*", "gi")
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.spellLibraryDialog.events({
|
||||||
|
"click .cancelButton": function(event, template){
|
||||||
|
popDialogStack();
|
||||||
|
},
|
||||||
|
"click .okButton": function(event, template){
|
||||||
|
popDialogStack(template.selectedSpell.get());
|
||||||
|
},
|
||||||
|
"click .library-spell": function(event, template){
|
||||||
|
template.selectedSpell.set(this.spell);
|
||||||
|
},
|
||||||
|
"click #backButton": function(event, template){
|
||||||
|
popDialogStack();
|
||||||
|
},
|
||||||
|
"click .category-header": function(event, template){
|
||||||
|
let cats = template.categoriesOpen.get();
|
||||||
|
const key = this.key;
|
||||||
|
// Toggle whether this key is in the array or not
|
||||||
|
if (_.contains(cats, key)){
|
||||||
|
cats = _.without(cats, key);
|
||||||
|
} else {
|
||||||
|
cats.push(key);
|
||||||
|
}
|
||||||
|
template.categoriesOpen.set(cats);
|
||||||
|
},
|
||||||
|
"input .search-input, change .search-input": function(event, template){
|
||||||
|
const value = event.currentTarget.value;
|
||||||
|
template.searchTerm.set(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
<paper-diff-slider class="tempHitPointSlider flex"
|
<paper-diff-slider class="tempHitPointSlider flex"
|
||||||
value={{left}}
|
|
||||||
max={{maximum}}
|
max={{maximum}}
|
||||||
|
value={{left}}
|
||||||
editable pin
|
editable pin
|
||||||
></paper-diff-slider>
|
></paper-diff-slider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,18 @@
|
|||||||
<td>Total</td>
|
<td>Total</td>
|
||||||
<td>{{characterCalculate "skillMod" charId skillName}}</td>
|
<td>{{characterCalculate "skillMod" charId skillName}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{#each passiveEffects}}
|
||||||
|
<tr>
|
||||||
|
<td>{{sourceName}}</td>
|
||||||
|
<td>Passive Bonus: {{statValue}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
{{#if showPassiveTotal}}
|
||||||
|
<tr class="paper-font-body2">
|
||||||
|
<td>Passive Score</td>
|
||||||
|
<td>{{characterCalculate "passiveSkill" charId skillName}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Template.skillDialogView.helpers({
|
|||||||
var char = Characters.findOne(this.charId);
|
var char = Characters.findOne(this.charId);
|
||||||
if (!char) return;
|
if (!char) return;
|
||||||
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
|
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
|
||||||
var proficiencyBonus =
|
var proficiencyBonus =
|
||||||
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
|
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
|
||||||
return prof * proficiencyBonus;
|
return prof * proficiencyBonus;
|
||||||
},
|
},
|
||||||
@@ -189,6 +189,23 @@ Template.skillDialogView.helpers({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
passiveEffects: function(){
|
||||||
|
return Effects.find({
|
||||||
|
charId: this.charId,
|
||||||
|
stat: this.skillName,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showPassiveTotal: function(){
|
||||||
|
if (this.skillName === "perception") return true;
|
||||||
|
return Effects.find({
|
||||||
|
charId: this.charId,
|
||||||
|
stat: this.skillName,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
}).count();
|
||||||
|
},
|
||||||
ability: function(){
|
ability: function(){
|
||||||
var opts = {fields: {}};
|
var opts = {fields: {}};
|
||||||
opts.fields[this.skillName] = 1;
|
opts.fields[this.skillName] = 1;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
{{#if conditionalCount}}
|
{{#if conditionalCount}}
|
||||||
*
|
*
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if showPassive}}
|
{{#if isPassiveShown}}
|
||||||
({{characterCalculate "passiveSkill" ../_id skill}})
|
({{characterCalculate "passiveSkill" ../_id skill}})
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,4 +38,16 @@ Template.skillRow.helpers({
|
|||||||
operation: "conditional",
|
operation: "conditional",
|
||||||
}).count();
|
}).count();
|
||||||
},
|
},
|
||||||
|
isPassiveShown: function(){
|
||||||
|
if (this.showPassive === "forced") return true;
|
||||||
|
if (this.showPassive === "ifNeeded"){
|
||||||
|
var charId = Template.parentData()._id;
|
||||||
|
return Effects.find({
|
||||||
|
charId,
|
||||||
|
stat: this.skill,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
}).count();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,24 +49,24 @@
|
|||||||
Skills
|
Skills
|
||||||
</div>
|
</div>
|
||||||
<div flex class="bottom list">
|
<div flex class="bottom list">
|
||||||
{{> skillRow name="Acrobatics" skill="acrobatics"}}
|
{{> skillRow name="Acrobatics" skill="acrobatics" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Animal Handling" skill="animalHandling"}}
|
{{> skillRow name="Animal Handling" skill="animalHandling" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Arcana" skill="arcana"}}
|
{{> skillRow name="Arcana" skill="arcana" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Athletics" skill="athletics"}}
|
{{> skillRow name="Athletics" skill="athletics" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Deception" skill="deception"}}
|
{{> skillRow name="Deception" skill="deception" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="History" skill="history"}}
|
{{> skillRow name="History" skill="history" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Insight" skill="insight"}}
|
{{> skillRow name="Insight" skill="insight" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Intimidation" skill="intimidation"}}
|
{{> skillRow name="Intimidation" skill="intimidation" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Investigation" skill="investigation"}}
|
{{> skillRow name="Investigation" skill="investigation" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Medicine" skill="medicine"}}
|
{{> skillRow name="Medicine" skill="medicine" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Nature" skill="nature"}}
|
{{> skillRow name="Nature" skill="nature" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Perception" skill="perception" showPassive="true"}}
|
{{> skillRow name="Perception" skill="perception" showPassive="forced"}}
|
||||||
{{> skillRow name="Performance" skill="performance"}}
|
{{> skillRow name="Performance" skill="performance" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Persuasion" skill="persuasion"}}
|
{{> skillRow name="Persuasion" skill="persuasion" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Religion" skill="religion"}}
|
{{> skillRow name="Religion" skill="religion" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Sleight of Hand" skill="sleightOfHand"}}
|
{{> skillRow name="Sleight of Hand" skill="sleightOfHand" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Stealth" skill="stealth"}}
|
{{> skillRow name="Stealth" skill="stealth" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Survival" skill="survival"}}
|
{{> skillRow name="Survival" skill="survival" showPassive="ifNeeded"}}
|
||||||
</div>
|
</div>
|
||||||
</paper-material>
|
</paper-material>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{#if characters.count}}
|
{{#if characters.count}}
|
||||||
<div class="character-list layout horizontal wrap">
|
<div class="character-list layout horizontal wrap">
|
||||||
{{# each characters}}
|
{{# each characters}}
|
||||||
<a class="character-card flex layout vertical end-justified" href="/character/{{_id}}">
|
<a class="character-card flex layout vertical end-justified" href="{{pathFor route="characterSheet" data=this}}">
|
||||||
<iron-image class="fit {{colorClass}}"
|
<iron-image class="fit {{colorClass}}"
|
||||||
sizing="cover" preload fade src={{picture}}>
|
sizing="cover" preload fade src={{picture}}>
|
||||||
</iron-image>
|
</iron-image>
|
||||||
|
|||||||
@@ -10,7 +10,15 @@ Template.characterList.helpers({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: {name: 1, picture: 1, color: 1, race: 1, alignment: 1, gender: 1},
|
fields: {
|
||||||
|
name: 1,
|
||||||
|
urlName: 1,
|
||||||
|
picture: 1,
|
||||||
|
color: 1,
|
||||||
|
race: 1,
|
||||||
|
alignment: 1,
|
||||||
|
gender: 1,
|
||||||
|
},
|
||||||
sort: {name: 1},
|
sort: {name: 1},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Template.characterSideList.helpers({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: {name: 1},
|
fields: {name: 1, urlName: 1},
|
||||||
sort: {name: 1},
|
sort: {name: 1},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,10 +21,15 @@
|
|||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro .section .columns {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro paper-button {
|
.intro paper-button {
|
||||||
min-width: 200px;
|
flex-basis: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="section white-text" style="background: #282828">
|
<div class="section white-text" style="background: #282828">
|
||||||
<div class="columns layout horizontal around-justified wrap">
|
<div class="columns layout horizontal around-justified wrap">
|
||||||
<div>
|
<div class="layout vertical center">
|
||||||
<div class="paper-font-headline">
|
<div class="paper-font-headline">
|
||||||
Guide
|
Guide
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</paper-button>
|
</paper-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="layout vertical center">
|
||||||
<div class="paper-font-headline">
|
<div class="paper-font-headline">
|
||||||
Discuss
|
Discuss
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
</paper-button>
|
</paper-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="layout vertical center">
|
||||||
<div class="paper-font-headline">
|
<div class="paper-font-headline">
|
||||||
Get involved
|
Get involved
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</app-toolbar>
|
</app-toolbar>
|
||||||
<div class="form flex scroll-y">
|
<div class="form flex scroll-y" style="position: relative;">
|
||||||
{{#unless editing}}
|
{{#unless editing}}
|
||||||
{{> UI.contentBlock}}
|
{{> UI.contentBlock}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<template name="baseEditDialog">
|
||||||
|
<div class="fit base-dialog layout vertical">
|
||||||
|
<app-toolbar class={{class}}>
|
||||||
|
<paper-icon-button id="backButton"
|
||||||
|
icon="arrow-back">
|
||||||
|
</paper-icon-button>
|
||||||
|
<div main-title>{{title}}</div>
|
||||||
|
{{#unless hideDelete}}
|
||||||
|
<paper-icon-button id="deleteButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
icon="delete">
|
||||||
|
</paper-icon-button>
|
||||||
|
{{/unless}}
|
||||||
|
{{#unless hideColor}}
|
||||||
|
{{> colorDropdown}}
|
||||||
|
{{/unless}}
|
||||||
|
</app-toolbar>
|
||||||
|
<div class="form flex scroll-y" style="position: relative;">
|
||||||
|
{{> UI.contentBlock}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Template.baseEditDialog.events({
|
||||||
|
"tap #backButton": function(){
|
||||||
|
popDialogStack();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -13,14 +13,80 @@ pushDialogStack = function({template, data, element, returnElement, callback}){
|
|||||||
returnElement,
|
returnElement,
|
||||||
callback,
|
callback,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateHistory();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var currentResult;
|
||||||
|
|
||||||
popDialogStack = function(result){
|
popDialogStack = function(result){
|
||||||
|
if (history && history.state && history.state.openDialogs){
|
||||||
|
currentResult = result;
|
||||||
|
history.back();
|
||||||
|
} else {
|
||||||
|
popDialogStackAction(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onpopstate = function(event){
|
||||||
|
let state = event.state;
|
||||||
|
let numDialogs = dialogs._array.length;
|
||||||
|
if (_.isFinite(state.openDialogs) && numDialogs > state.openDialogs){
|
||||||
|
popDialogStackAction(currentResult);
|
||||||
|
currentResult = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
popDialogStackAction = function(result){
|
||||||
const dialog = dialogs.pop();
|
const dialog = dialogs.pop();
|
||||||
|
updateHistory();
|
||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
dialog.callback && dialog.callback(result);
|
dialog.callback && dialog.callback(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let updateHistory = function(){
|
||||||
|
// history should looks like: [{openDialogs: 0}, {openDialogs: n}] where
|
||||||
|
// n is the number of open dialogs
|
||||||
|
|
||||||
|
// If we can't access the history object, give up
|
||||||
|
if (!history) return;
|
||||||
|
// Make sure that there is a state tracking open dialogs
|
||||||
|
// replace the state without bashing it in the process
|
||||||
|
if (!history.state || !_.isFinite(history.state.openDialogs)){
|
||||||
|
let newState = _.clone(history.state) || {};
|
||||||
|
newState.openDialogs = 0;
|
||||||
|
history.replaceState(newState, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const numDialogs = dialogs._array.length;
|
||||||
|
const stateDialogs = history.state.openDialogs;
|
||||||
|
|
||||||
|
// If the number of dialogs and state dialogs are equal, we don't need to do
|
||||||
|
// anything
|
||||||
|
if (numDialogs === stateDialogs) return;
|
||||||
|
|
||||||
|
if (stateDialogs > 0){
|
||||||
|
// On a dialog count
|
||||||
|
if (numDialogs === 0){
|
||||||
|
// but shouldn't be
|
||||||
|
history.back();
|
||||||
|
} else {
|
||||||
|
// but should replace with correct count
|
||||||
|
let newState = _.clone(history.state) || {};
|
||||||
|
newState.openDialogs = dialogs._array.length;
|
||||||
|
history.replaceState(newState, "");
|
||||||
|
}
|
||||||
|
} else if (numDialogs > 0 && stateDialogs === 0){
|
||||||
|
// On the zero state, push a dialog count
|
||||||
|
history.pushState({openDialogs: numDialogs}, "");
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"History could not be updated correctly, unexpected case",
|
||||||
|
{stateDialogs, numDialogs},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Template.dialogStack.helpers({
|
Template.dialogStack.helpers({
|
||||||
dialogStackClass(){
|
dialogStackClass(){
|
||||||
if (!dialogs.get().length) return "hide";
|
if (!dialogs.get().length) return "hide";
|
||||||
@@ -174,7 +240,7 @@ const dialogCloseAnimation = ({element, returnElement, dialog, callback}) => {
|
|||||||
const stackCompensation = dialogs._array.length ? 16 : 0;
|
const stackCompensation = dialogs._array.length ? 16 : 0;
|
||||||
|
|
||||||
// Insert clone before its progenitor so it can inherit css correctly
|
// Insert clone before its progenitor so it can inherit css correctly
|
||||||
element.parentNode.insertBefore(clone, element);
|
element.parentNode && element.parentNode.insertBefore(clone, element);
|
||||||
|
|
||||||
// Polymer messes up fixed positioning, measure and compensate
|
// Polymer messes up fixed positioning, measure and compensate
|
||||||
startingRect = clone.getBoundingClientRect();
|
startingRect = clone.getBoundingClientRect();
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ Template.fabMenu.helpers({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Template.fabMenu.events({
|
Template.fabMenu.events({
|
||||||
"tap .expand-menu": function(event, instance) {
|
"click .expand-menu": function(event, instance) {
|
||||||
instance.active.set(!instance.active.get());
|
instance.active.set(!instance.active.get());
|
||||||
},
|
},
|
||||||
"tap .mini-holder paper-fab": function(event, instance) {
|
"click .mini-holder paper-fab": function(event, instance) {
|
||||||
instance.active.set(false);
|
instance.active.set(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
71
rpg-docs/lib/constants/statOrder.js
Normal file
71
rpg-docs/lib/constants/statOrder.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
statOrder = {
|
||||||
|
"strength": 1,
|
||||||
|
"dexterity": 2,
|
||||||
|
"constitution": 3,
|
||||||
|
"intelligence": 4,
|
||||||
|
"wisdom": 5,
|
||||||
|
"charisma": 6,
|
||||||
|
"strengthSave": 7,
|
||||||
|
"dexteritySave": 8,
|
||||||
|
"constitutionSave": 9,
|
||||||
|
"intelligenceSave": 10,
|
||||||
|
"wisdomSave": 11,
|
||||||
|
"charismaSave": 12,
|
||||||
|
"acrobatics": 13,
|
||||||
|
"animalHandling": 14,
|
||||||
|
"arcana": 15,
|
||||||
|
"athletics": 16,
|
||||||
|
"deception": 17,
|
||||||
|
"history": 18,
|
||||||
|
"insight": 19,
|
||||||
|
"intimidation": 20,
|
||||||
|
"investigation": 21,
|
||||||
|
"medicine": 22,
|
||||||
|
"nature": 23,
|
||||||
|
"perception": 24,
|
||||||
|
"performance": 25,
|
||||||
|
"persuasion": 26,
|
||||||
|
"religion": 27,
|
||||||
|
"sleightOfHand": 28,
|
||||||
|
"stealth": 29,
|
||||||
|
"survival": 30,
|
||||||
|
"initiative": 31,
|
||||||
|
"hitPoints": 32,
|
||||||
|
"armor": 33,
|
||||||
|
"dexterityArmor": 34,
|
||||||
|
"speed": 35,
|
||||||
|
"proficiencyBonus": 36,
|
||||||
|
"ki": 37,
|
||||||
|
"sorceryPoints": 38,
|
||||||
|
"rages": 39,
|
||||||
|
"rageDamage": 40,
|
||||||
|
"expertiseDice": 41,
|
||||||
|
"superiorityDice": 42,
|
||||||
|
"carryMultiplier": 43,
|
||||||
|
"level1SpellSlots": 44,
|
||||||
|
"level2SpellSlots": 45,
|
||||||
|
"level3SpellSlots": 46,
|
||||||
|
"level4SpellSlots": 47,
|
||||||
|
"level5SpellSlots": 48,
|
||||||
|
"level6SpellSlots": 49,
|
||||||
|
"level7SpellSlots": 50,
|
||||||
|
"level8SpellSlots": 51,
|
||||||
|
"level9SpellSlots": 52,
|
||||||
|
"d6HitDice": 53,
|
||||||
|
"d8HitDice": 54,
|
||||||
|
"d10HitDice": 55,
|
||||||
|
"d12HitDice": 56,
|
||||||
|
"acidMultiplier": 57,
|
||||||
|
"bludgeoningMultiplier": 58,
|
||||||
|
"coldMultiplier": 59,
|
||||||
|
"fireMultiplier": 60,
|
||||||
|
"forceMultiplier": 61,
|
||||||
|
"lightningMultiplier": 62,
|
||||||
|
"necroticMultiplier": 63,
|
||||||
|
"piercingMultiplier": 64,
|
||||||
|
"poisonMultiplier": 65,
|
||||||
|
"psychicMultiplier": 66,
|
||||||
|
"radiantMultiplier": 67,
|
||||||
|
"slashingMultiplier": 68,
|
||||||
|
"thunderMultiplier": 69,
|
||||||
|
};
|
||||||
@@ -8,9 +8,10 @@
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
//evaluates a calculation string
|
//evaluates a calculation string
|
||||||
evaluate = function(charId, string){
|
evaluate = function(charId, string, opts){
|
||||||
|
var spellListId = opts && opts.spellListId;
|
||||||
if (!string) return string;
|
if (!string) return string;
|
||||||
string = string.replace(/\b[a-z]+\b/gi, function(sub){
|
string = string.replace(/\b[a-z,1-9]+\b/gi, function(sub){
|
||||||
//fields
|
//fields
|
||||||
if (Schemas.Character.schema(sub)){
|
if (Schemas.Character.schema(sub)){
|
||||||
return Characters.calculate.fieldValue(charId, sub);
|
return Characters.calculate.fieldValue(charId, sub);
|
||||||
@@ -43,6 +44,12 @@ evaluate = function(charId, string){
|
|||||||
if (sub.toUpperCase() === "LEVEL"){
|
if (sub.toUpperCase() === "LEVEL"){
|
||||||
return Characters.calculate.level(charId);
|
return Characters.calculate.level(charId);
|
||||||
}
|
}
|
||||||
|
if (spellListId && sub.toUpperCase() === "DC") {
|
||||||
|
var list = SpellLists.findOne(spellListId);
|
||||||
|
if (list && list.saveDC){
|
||||||
|
return evaluate(charId, list.saveDC);
|
||||||
|
}
|
||||||
|
}
|
||||||
return sub;
|
return sub;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@@ -66,6 +73,17 @@ evaluateString = function(charId, string){
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
evaluateSpellString = function (charId, spellListId, string) {
|
||||||
|
//define brackets as curly brackets around anything that isn't a curly bracket
|
||||||
|
if (!string) return string;
|
||||||
|
var brackets = /\{[^\{\}]*\}/g;
|
||||||
|
var result = string.replace(brackets, function(exp){
|
||||||
|
exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets
|
||||||
|
return evaluate(charId, exp, {spellListId});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
//returns the value of the effect if it exists,
|
//returns the value of the effect if it exists,
|
||||||
//otherwise returns the result of the calculation if it exists,
|
//otherwise returns the result of the calculation if it exists,
|
||||||
//otherwise returns 0
|
//otherwise returns 0
|
||||||
|
|||||||
@@ -249,7 +249,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
_updateItems: function() {
|
_updateItems: function() {
|
||||||
var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*');
|
var nodes = this.selectable
|
||||||
|
? Polymer.dom(this).querySelectorAll(this.selectable)
|
||||||
|
: Polymer.dom(this).queryDistributedElements('*');
|
||||||
nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
|
nodes = Array.prototype.filter.call(nodes, this._bindFilterItem);
|
||||||
this._setItems(nodes);
|
this._setItems(nodes);
|
||||||
},
|
},
|
||||||
|
|||||||
45
rpg-docs/server/lib/cron/deleteRemovedDocuments.js
Normal file
45
rpg-docs/server/lib/cron/deleteRemovedDocuments.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
Meteor.startup(() => {
|
||||||
|
const collections = [
|
||||||
|
Attacks, Buffs, Classes, Effects, Experiences,
|
||||||
|
Features, Notes, Proficiencies, SpellLists, Spells,
|
||||||
|
Containers, Items,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all soft removed documents that were removed more than 30 minutes ago
|
||||||
|
* and were not restored
|
||||||
|
* @return {Number} Number of documents removed
|
||||||
|
*/
|
||||||
|
const deleteOldSoftRemovedDocs = function(){
|
||||||
|
let numRemoved = 0;
|
||||||
|
const now = new Date();
|
||||||
|
const thirtyMinutesAgo = new Date(now.getTime() - 30*60000);
|
||||||
|
_.each(collections, (collection) => {
|
||||||
|
numRemoved += collection.remove({
|
||||||
|
removed: true,
|
||||||
|
removedAt: {$lt: thirtyMinutesAgo} // dates *before* 30 minutes ago
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return numRemoved;
|
||||||
|
};
|
||||||
|
|
||||||
|
SyncedCron.add({
|
||||||
|
name: "Delete all soft removed items that haven't been restored",
|
||||||
|
schedule: function(parser) {
|
||||||
|
return parser.text('every 6 hours');
|
||||||
|
},
|
||||||
|
job: function() {
|
||||||
|
deleteOldSoftRemovedDocs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a method to manually trigger removal
|
||||||
|
Meteor.methods({
|
||||||
|
deleteOldSoftRemovedDocs() {
|
||||||
|
const user = Meteor.users.findOne(this.userId);
|
||||||
|
if (user && _.contains(user.roles, "admin")){
|
||||||
|
return deleteOldSoftRemovedDocs();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -125,7 +125,7 @@ Migrations.add({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
var effect = Effects.findOne({
|
var effect = Effects.findOne({
|
||||||
charId: char._id, name: "Natural Carrying Capacity"
|
charId: char._id, name: "Natural Carrying Capacity",
|
||||||
});
|
});
|
||||||
if (effect) return;
|
if (effect) return;
|
||||||
Effects.insert({
|
Effects.insert({
|
||||||
@@ -141,7 +141,7 @@ Migrations.add({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
effect = Effects.findOne({
|
effect = Effects.findOne({
|
||||||
charId: char._id, name: "Natural Carrying Capacity"
|
charId: char._id, name: "Natural Carrying Capacity",
|
||||||
});
|
});
|
||||||
if (!effect) throw "Carry capacity effect should be set by now."
|
if (!effect) throw "Carry capacity effect should be set by now."
|
||||||
});
|
});
|
||||||
@@ -150,3 +150,19 @@ Migrations.add({
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Migrations.add({
|
||||||
|
version: 5,
|
||||||
|
name: "Gives all characters a URL name",
|
||||||
|
up: function() {
|
||||||
|
//update characters
|
||||||
|
Characters.find({}).forEach(function(char){
|
||||||
|
if (char.urlName) return;
|
||||||
|
var urlName = getSlug(char.name, {maintainCase: true});
|
||||||
|
Characters.update(char._id, {$set: {urlName}});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
down: function(){
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Meteor.publish("characterList", function(){
|
|||||||
{
|
{
|
||||||
fields: {
|
fields: {
|
||||||
name: 1,
|
name: 1,
|
||||||
|
urlName: 1,
|
||||||
race: 1,
|
race: 1,
|
||||||
alignment: 1,
|
alignment: 1,
|
||||||
gender: 1,
|
gender: 1,
|
||||||
|
|||||||
@@ -14,3 +14,12 @@ Meteor.publish("standardLibraryItems", function(categoryKey){
|
|||||||
sort: {name: 1},
|
sort: {name: 1},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Meteor.publish("standardLibrarySpells", function(level){
|
||||||
|
return LibrarySpells.find({
|
||||||
|
library: {$in: standardLibraryIds},
|
||||||
|
level,
|
||||||
|
}, {
|
||||||
|
sort: {name: 1},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user