Compare commits

...

9 Commits
1.3.1 ... 1.3.2

Author SHA1 Message Date
Stefan Zermatten
54f055d3ef Merge branch 'bugfix-model' 2017-07-13 16:03:23 +02:00
Stefan Zermatten
2bacb296ba Fixed new effects dialog animation
makes some progress on #72
2017-07-13 16:01:43 +02:00
Stefan Zermatten
8b061f7aa9 Improved look and feel of effect editing 2017-07-13 15:53:03 +02:00
Stefan Zermatten
6a84c83644 Made passive scores show up for all skills with bonuses to passive score
closes #56
2017-07-13 13:42:04 +02:00
Stefan Zermatten
3227cd0934 Add character names to the end of their URL's
Closes #21, makes links to characters human readable
2017-07-13 13:14:04 +02:00
Stefan Zermatten
089feae26f Spell slots are now available in calculations
closes #11
2017-07-13 10:56:42 +02:00
Stefan Zermatten
99c72d1e10 Made names optional for all collections
closes #92
2017-07-13 10:39:43 +02:00
Stefan Zermatten
1e67afbe6f Made DC available in spell descriptions as a variable
closes #101
2017-07-13 10:30:16 +02:00
Stefan Zermatten
1cfec1ca45 Allowed carryMultiplier to be a decimal
closes #100
2017-07-13 09:50:20 +02:00
33 changed files with 256 additions and 79 deletions

View File

@@ -48,3 +48,4 @@ es5-shim@4.6.15
differential:vulcanize differential:vulcanize
reactive-dict reactive-dict
percolate:synced-cron percolate:synced-cron
ongoworks:speakingurl

View File

@@ -85,6 +85,7 @@ 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 percolate:synced-cron@1.3.2

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -8,6 +8,7 @@ Schemas.Buff = new SimpleSchema({
}, },
name: { name: {
type: String, type: String,
optional: true,
trim: false, trim: false,
}, },
description: { description: {

View File

@@ -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},
@@ -257,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);
}); });
@@ -537,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({

View File

@@ -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,

View File

@@ -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: {

View File

@@ -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},

View File

@@ -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,

View File

@@ -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},

View File

@@ -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",
}, },

View File

@@ -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},

View File

@@ -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

View File

@@ -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() {

View File

@@ -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(

View 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);
};

View File

@@ -20,26 +20,28 @@
<div class="effectEdit layout horizontal flex"> <div class="effectEdit layout horizontal flex">
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;"> <dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
{{#each statGroups}} {{#each statGroups}}
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;"> <div class="statGroupTitle clickable" style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
{{this}} {{this}}
</div> </div>
{{#each stats}} <iron-collapse opened={{isGroupSelected this ../stat}}>
<paper-item name={{stat}}>{{name}}</paper-item> {{#each stats}}
{{/each}} <paper-item name={{stat}} class="clickable">{{name}}</paper-item>
{{/each}}
</iron-collapse>
{{/each}} {{/each}}
</dicecloud-selector> </dicecloud-selector>
{{#if operations}} {{#if operations}}
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;"> <dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
{{#each operations}} {{#each operations}}
<paper-item name={{operation}}>{{name}}</paper-item> <paper-item name={{operation}} class="clickable">{{name}}</paper-item>
{{/each}} {{/each}}
</dicecloud-selector> </dicecloud-selector>
{{else}} {{#if showMultiplierOperations}} {{else}} {{#if showMultiplierOperations}}
<dicecloud-selector class="multiplierMenu flex" <dicecloud-selector class="multiplierMenu flex"
selected={{value}}> selected={{value}}>
<paper-item name="0.5">Resistance</paper-item> <paper-item name="0.5" class="clickable">Resistance</paper-item>
<paper-item name="2">Vulnerability</paper-item> <paper-item name="2" class="clickable">Vulnerability</paper-item>
<paper-item name="0">Immunity</paper-item> <paper-item name="0" class="clickable">Immunity</paper-item>
</dicecloud-selector> </dicecloud-selector>
{{else}} {{else}}
<div class="flex" style="height: 100%;"></div> <div class="flex" style="height: 100%;"></div>

View File

@@ -149,47 +149,71 @@ Template.effectEdit.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 #deleteButton": function(event, instance){ "click #deleteButton": function(event, instance){
Effects.softRemoveNode(instance.data.id); Effects.softRemoveNode(instance.data.id);
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect"); GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
popDialogStack(); popDialogStack();
}, },
"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){ "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;
var setter = {stat: statName}; setStat(statName, this._id);
var group = Blaze.getData(detail.item).group;
var effect = Effects.findOne(this._id);
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(this._id, {$set: setter});
}, },
"iron-select .operationMenu": function(event){ "iron-select .operationMenu": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;

View File

@@ -33,7 +33,7 @@ Template.effectsEditList.events({
template: "effectEdit", template: "effectEdit",
data: {id: effectId}, data: {id: effectId},
element: event.currentTarget, element: event.currentTarget,
returnElement: instance.find(`tr.effect[data-id='${effectId}']`), returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`),
}); });
}, },
"tap .edit-effect": function(event, template){ "tap .edit-effect": function(event, template){

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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();
}
},
}); });

View File

@@ -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>

View File

@@ -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>

View File

@@ -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},
} }
); );

View File

@@ -14,7 +14,7 @@ Template.characterSideList.helpers({
] ]
}, },
{ {
fields: {name: 1}, fields: {name: 1, urlName: 1},
sort: {name: 1}, sort: {name: 1},
} }
); );

View File

@@ -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

View File

@@ -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);
}, },

View File

@@ -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;
},
});

View File

@@ -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,