Compare commits

..

16 Commits
0.5.7 ... 0.6.1

Author SHA1 Message Date
Stefan Zermatten
4f60766d5d Merge branch 'hotfix-experience-edit-inputs' 2015-06-25 14:07:34 +02:00
Stefan Zermatten
e992aeebef Bumped version 2015-06-25 14:07:24 +02:00
Stefan Zermatten
4108346a98 Fixed experience inputs updating 2015-06-25 14:07:13 +02:00
Stefan Zermatten
946fadadc2 Merge branch 'release-0.6.0' 2015-06-25 13:49:25 +02:00
Stefan Zermatten
af57326194 Bumped version 2015-06-25 13:48:12 +02:00
Stefan Zermatten
98c69e9e17 Style tweaks 2015-06-25 13:38:33 +02:00
Stefan Zermatten
395edd0563 Character settings now formatted and has done button 2015-06-25 13:38:20 +02:00
Stefan Zermatten
43e87e7786 Container weight summaries now round off 2015-06-25 13:37:55 +02:00
Stefan Zermatten
ad347504c6 Fixed experienceDialog updating polymer inputs 2015-06-25 13:37:03 +02:00
Stefan Zermatten
4e6e99b695 Fixed spellDialog formatting 2015-06-25 13:36:42 +02:00
Stefan Zermatten
104624a322 Merge branch 'feature-soft-memoize' into develop 2015-06-25 13:35:19 +02:00
Stefan Zermatten
79d166e6af Removed references to old calculations 2015-06-25 13:34:44 +02:00
Stefan Zermatten
86c934e8ac Began replacing calls to helpers with calls to memoized functions 2015-06-18 15:14:37 +02:00
Stefan Zermatten
a034cbf30e Duplicate character helpers with memoized functions not attached to characters 2015-06-18 13:34:59 +02:00
Stefan Zermatten
d5680ebf8a Add memoize functionality 2015-06-18 13:33:54 +02:00
Stefan Zermatten
53f2fcc945 Merge branch 'release-0.5.7' into develop 2015-06-17 14:32:35 +02:00
35 changed files with 506 additions and 323 deletions

View File

@@ -3,16 +3,16 @@ Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({ Schemas.Character = new SimpleSchema({
//strings //strings
name: {type: String, defaultValue: "", trim: false}, name: {type: String, defaultValue: "", trim: false, optional: true},
alignment: {type: String, defaultValue: "", trim: false}, alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: {type: String, defaultValue: "", trim: false}, gender: {type: String, defaultValue: "", trim: false, optional: true},
race: {type: String, defaultValue: "", trim: false}, race: {type: String, defaultValue: "", trim: false, optional: true},
description: {type: String, defaultValue: "", trim: false}, description: {type: String, defaultValue: "", trim: false, optional: true},
personality: {type: String, defaultValue: "", trim: false}, personality: {type: String, defaultValue: "", trim: false, optional: true},
ideals: {type: String, defaultValue: "", trim: false}, ideals: {type: String, defaultValue: "", trim: false, optional: true},
bonds: {type: String, defaultValue: "", trim: false}, bonds: {type: String, defaultValue: "", trim: false, optional: true},
flaws: {type: String, defaultValue: "", trim: false}, flaws: {type: String, defaultValue: "", trim: false, optional: true},
backstory: {type: String, defaultValue: "", trim: false}, backstory: {type: String, defaultValue: "", trim: false, optional: true},
//attributes //attributes
//ability scores //ability scores
@@ -190,91 +190,101 @@ var attributeBase = function(charId, statName){
check(statName, String); check(statName, String);
//if it's a damage multiplier, we treat it specially //if it's a damage multiplier, we treat it specially
if (_.contains(DAMAGE_MULTIPLIERS, statName)){ if (_.contains(DAMAGE_MULTIPLIERS, statName)){
var effects = Effects.find( var invulnerabilityCount = Effects.find({
{charId: charId, stat: statName, enabled: true, operation: "mul"} charId: charId,
).fetch(); stat: statName,
var resistCount = 0; enabled: true,
var vulnCount = 0; operation: "mul",
var multiplierEvaluationFail = false; value: 0,
_.each(effects, function(effect){ }).count();
var val = evaluateEffect(charId, effect); if (invulnerabilityCount) return 0;
if (val === 0.5){ //resistance var resistCount = Effects.find({
resistCount += 1; charId: charId,
} else if (val === 2){ //vulnerability stat: statName,
vulnCount += 1; enabled: true,
} else if (val === 0){ //imunity operation: "mul",
return 0; //imunity is absolute value: 0.5,
} else { }).count();
multiplierEvaluationFail = true; var vulnCount = Effects.find({
} charId: charId,
}); stat: statName,
if (multiplierEvaluationFail){ enabled: true,
//we can't work it out correctly, set the value to 1 operation: "mul",
//and try work it out using regular maths below value: 2,
value = 1; }).count();
} else if (resistCount && !vulnCount){ if (!resistCount && !vulnCount){
return 1;
} else if (resistCount && !vulnCount){
return 0.5; return 0.5;
} else if (!resistCount && vulnCount){ } else if (!resistCount && vulnCount){
return 2; return 2;
} else { } else {
return 1; return 1;
} }
} }
var value;
var base = 0;
var add = 0;
var mul = 1;
var min = Math.NEGATIVE_INFINITY;
var max = Math.POSITIVE_INFINITY;
var value = 0; Effects.find({
charId: charId,
//start with the highest base value stat: statName,
Effects.find( enabled: true,
{charId: charId, stat: statName, enabled: true, operation: "base"} operation: {$in: ["base", "add", "mul", "min", "max"]},
).forEach(function(effect){ }).forEach(function(effect) {
var efv = evaluateEffect(charId, effect); value = evaluateEffect(charId, effect);
if (efv > value){ if (effect.operation === "base"){
value = efv; if (value > base) base = value;
} else if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
} }
}); });
//add all the add values var result = (base + add) * mul;
Effects.find( if (result < min) result = min;
{charId: charId, stat: statName, enabled: true, operation: "add"} if (result > max) result = max;
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
//multiply all the mul values return result;
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "mul"}
).forEach(function(effect){
value *= evaluateEffect(charId, effect);
});
//ensure value is >= all mins
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "min"}
).forEach(function(effect){
var min = evaluateEffect(charId, effect);
value = value > min ? value : min;
});
//ensure value is <= all maxes
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "max"}
).forEach(function(effect){
var max = evaluateEffect(charId, effect);
value = value < max ? value : max;
});
return value;
}; };
//functions and calculated values. if (Meteor.isClient) {
//These functions can only rely on this._id since no other Template.registerHelper("characterCalculate", function(func, charId, input) {
//field is likely to be attached to all returned characters try {
Characters.helpers({ return Characters.calculate[func](charId, input);
//returns the value stored in the field requested } catch (e){
//will set up dependencies on just that field if (!Characters.calculate[func]){
getField : function(fieldName){ throw new Error(func + "is not a function name");
} else {
throw e;
}
}
});
}
//create a local memoize with a argument concatenating hash function
var memoize = function(f) {
return Tracker.memoize(f, function() {
return _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
});
};
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {}; var fieldSelector = {};
fieldSelector[fieldName] = 1; fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector}); var char = Characters.findOne(charId, {fields: fieldSelector});
var field = char[fieldName]; var field = char[fieldName];
if (field === undefined){ if (field === undefined){
throw new Meteor.Error( throw new Meteor.Error(
@@ -287,8 +297,7 @@ Characters.helpers({
} }
return field; return field;
}, },
//returns the value of a field fieldValue: function(charId, fieldName) {
fieldValue : function(fieldName){
if (!Schemas.Character.schema(fieldName)){ if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error( throw new Meteor.Error(
"Field not found", "Field not found",
@@ -298,102 +307,92 @@ Characters.helpers({
//duck typing to get the right value function //duck typing to get the right value function
//.ability implies skill //.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){ if (Schemas.Character.schema(fieldName + ".ability")){
return this.skillMod(fieldName); return Characters.calculate.skillMod(charId, fieldName);
} }
//adjustment implies attribute //adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){ if (Schemas.Character.schema(fieldName + ".adjustment")){
return this.attributeValue(fieldName); return Characters.calculate.attributeValue(charId, fieldName);
} }
//fall back to just returning the field itself //fall back to just returning the field itself
return this.getField(fieldName); return Characters.calculate.getField(charId, fieldName);
}, },
attributeValue: memoize(function(charId, attributeName){
attributeValue: function(attributeName){ var attribute = Characters.calculate.getField(charId, attributeName);
var charId = this._id;
var attribute = this.getField(attributeName);
//base value //base value
var value = this.attributeBase(attributeName); var value = Characters.calculate.attributeBase(charId, attributeName);
//plus adjustment //plus adjustment
value += attribute.adjustment; value += attribute.adjustment;
return value; return value;
},
attributeBase: preventLoop(function(attributeName){
var charId = this._id;
//base value
return attributeBase(charId, attributeName);
}), }),
attributeBase: memoize(preventLoop(function(charId, attributeName){
skillMod: preventLoop(function(skillName){ return attributeBase(charId, attributeName);
var charId = this._id; })),
var skill = this.getField(skillName); skillMod: memoize(preventLoop(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
//get the final value of the ability score //get the final value of the ability score
var ability = this.attributeValue(skill.ability); var ability = Characters.calculate.attributeValue(charId, skill.ability);
//base modifier //base modifier
var mod = +getMod(ability); var mod = +getMod(ability);
//multiply proficiency bonus by largest value in proficiency array //multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skillName); var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier //add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus"); mod += prof * Characters.calculate.attributeValue(charId, "proficiencyBonus");
//apply all effects //apply all effects
var rawEffects = Effects.find( var value;
{charId: charId, stat: skillName, enabled: true} var add = 0;
).fetch(); var mul = 1;
var effects = _.groupBy(rawEffects, "operation"); var min = Math.NEGATIVE_INFINITY;
_.forEach(effects.add, function(effect){ var max = Math.POSITIVE_INFINITY;
mod += evaluateEffect(charId, effect);
});
_.forEach(effects.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
_.forEach(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min ? mod : min;
});
_.forEach(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max ? mod : max;
});
return signedString(mod);
}),
proficiency: function(skillName){ Effects.find({
var charId = this._id; charId: charId,
//return largest value in proficiency array stat: skillName,
var prof = 0; enabled: true,
Proficiencies.find( operation: {$in: ["base", "add", "mul", "min", "max"]},
{charId: charId, name: skillName, enabled: true} }).forEach(function(effect) {
).forEach(function(proficiency){ value = evaluateEffect(charId, effect);
var newProf = proficiency.value; if (effect.operation === "add"){
if (newProf > prof){ add += value;
prof = newProf; } else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
} }
}); });
return prof; var result = (mod + add) * mul;
}, if (result < min) result = min;
if (result > max) result = max;
passiveSkill: function(skillName){ return result;
if (_.isString(skillName)){ })),
var skill = this.getField(skillName); proficiency: memoize(function(charId, skillName){
} //return largest value in proficiency array
var charId = this._id; var prof = Proficiencies.findOne(
var mod = +this.skillMod(skillName); {charId: charId, name: skillName, enabled: true},
{sort: {value: -1}}
);
return prof && prof.value || 0;
}),
passiveSkill: memoize(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
var mod = +Characters.calculate.skillMod(charId, skillName);
var value = 10 + mod; var value = 10 + mod;
Effects.find( Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"} {charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}
).forEach(function(effect){ ).forEach(function(effect){
value += evaluateEffect(charId, effect); value += evaluateEffect(charId, effect);
}); });
var advantage = Characters.calculate.advantage(charId, skillName);
value += 5 * advantage;
return value; return value;
//TODO decide whether (dis)advantage gives (-)+5 to passive checks }),
}, advantage: memoize(function(charId, skillName){
advantage: function(skillName){
var charId = this._id;
var advantage = Effects.find( var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"} {charId: charId, stat: skillName, enabled: true, operation: "advantage"}
).count(); ).count();
@@ -403,19 +402,18 @@ Characters.helpers({
if (advantage && !disadvantage) return 1; if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1; if (disadvantage && !advantage) return -1;
return 0; return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
}, },
passiveAbility: function(charId, attribute){
abilityMod: function(attribute){ var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return signedString(getMod(this.attributeValue(attribute)));
},
passiveAbility: function(attribute){
var mod = +getMod(this.attributeValue(attribute));
return 10 + mod; return 10 + mod;
}, },
xpLevel: function(charId){
xpLevel: function(){ var xp = Characters.calculate.experience(charId);
var xp = this.experience();
for (var i = 0; i < 19; i++){ for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){ if (xp < XP_TABLE[i]){
return i; return i;
@@ -424,30 +422,103 @@ Characters.helpers({
if (xp > 355000) return 20; if (xp > 355000) return 20;
return 0; return 0;
}, },
level: memoize(function(charId){
level: function(){
var level = 0; var level = 0;
Classes.find({charId: this._id}).forEach(function(cls){ Classes.find({charId: charId}).forEach(function(cls){
level += cls.level; level += cls.level;
}); });
return level; return level;
}, }),
experience: memoize(function(charId){
experience: function(){
var xp = 0; var xp = 0;
Experiences.find( Experiences.find(
{charId: this._id}, {charId: charId},
{fields: {value: 1}} {fields: {value: 1}}
).forEach(function(e){ ).forEach(function(e){
xp += e.value; xp += e.value;
}); });
return xp; return xp;
}),
};
var depreciated = function() {
//var err = new Error("this function has been depreciated");
var name = "";
if (Template.instance()){
name = Template.instance().view.name;
}
var logString = "this function has been depreciated \n";
if (name){
logString += "View: " + name + "\n\n";
}
//logString += err.stack + "\n\n---------------------\n\n";
console.log(logString);
};
//functions and calculated values.
//These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters
Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
depreciated();
return Characters.calculate.getField(this._id, fieldName);
},
//returns the value of a field
fieldValue : function(fieldName){
depreciated();
return Characters.calculate.fieldValue(this._id, fieldName);
},
attributeValue: function(attributeName){
depreciated();
return Characters.calculate.attributeValue(this._id, attributeName);
},
attributeBase: function(attributeName){
depreciated();
return Characters.calculate.attributeBase(this._id, attributeName);
},
skillMod: function(skillName){
depreciated();
return Characters.calculate.skillMod(this._id, skillName);
},
proficiency: function(skillName){
depreciated();
return Characters.calculate.proficiency(this._id, skillName);
},
passiveSkill: function(skillName){
depreciated();
return Characters.calculate.passiveSkill(this._id, skillName);
},
advantage: function(skillName){
depreciated();
return Characters.calculate.advantage(this._id, skillName);
},
abilityMod: function(attribute){
depreciated();
return Characters.calculate.abilityMod(this._id, attribute);
},
passiveAbility: function(attribute){
depreciated();
return Characters.calculate.passiveAbility(this._id, attribute);
},
xpLevel: function(){
depreciated();
return Characters.calculate.xpLevel(this._id);
},
level: function(){
depreciated();
return Characters.calculate.level(this._id);
},
experience: function(){
depreciated();
return Characters.calculate.experience(this._id);
}, },
}); });
//clean up all data related to that character before removing it //clean up all data related to that character before removing it
Characters.after.remove(function(userId, character) { if (Meteor.isServer){
if (Meteor.isServer){ Characters.after.remove(function(userId, character) {
Actions .remove({charId: character._id}); Actions .remove({charId: character._id});
Attacks .remove({charId: character._id}); Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id}); Buffs .remove({charId: character._id});
@@ -460,8 +531,8 @@ Characters.after.remove(function(userId, character) {
SpellLists .remove({charId: character._id}); SpellLists .remove({charId: character._id});
Items .remove({charId: character._id}); Items .remove({charId: character._id});
Containers .remove({charId: character._id}); Containers .remove({charId: character._id});
} });
}); }
Characters.allow({ Characters.allow({
insert: function(userId, doc) { insert: function(userId, doc) {

View File

@@ -3,10 +3,6 @@
* Damage, healing and resource cost/recovery are all adjustments * Damage, healing and resource cost/recovery are all adjustments
*/ */
Schemas.Adjustment = new SimpleSchema({ Schemas.Adjustment = new SimpleSchema({
name: {
type: String,
optional: true,
},
//which stat the adjustment is applied to //which stat the adjustment is applied to
stat: { stat: {
type: String, type: String,

View File

@@ -91,6 +91,6 @@ $thinColumnWidth: 240px;
} }
/* undo pointer cursor on detail box heading */ /* undo pointer cursor on detail box heading */
#globalDetail .card .top { #globalDetail.card .top {
cursor: auto; cursor: auto;
} }

View File

@@ -15,4 +15,10 @@ td {
width: 250px; width: 250px;
} }
} }
} }
.summaryTable {
&:nth-child(3){
text-align: right;
}
}

View File

@@ -1,7 +1,7 @@
<template name="characterSettings"> <template name="characterSettings">
{{#with character}} {{#with character}}
<div> <div style="height: 100px;">
<table> <table style="width: 100%;">
<tr> <tr>
<td>Hide Spells tab</td> <td>Hide Spells tab</td>
<td> <td>
@@ -23,4 +23,5 @@
</table> </table>
</div> </div>
{{/with}} {{/with}}
<paper-button id="doneButton" affirmative> Done </paper-button>
</template> </template>

View File

@@ -20,7 +20,7 @@ Template.characterSheet.helpers({
hideSpellcasting: function() { hideSpellcasting: function() {
var char = Characters.findOne(this._id); var char = Characters.findOne(this._id);
return char && char.settings.hideSpellcasting; return char && char.settings.hideSpellcasting;
} },
}); });
Template.characterSheet.events({ Template.characterSheet.events({

View File

@@ -135,7 +135,7 @@
</template> </template>
<template name="resource"> <template name="resource">
{{#if char.attributeBase name}} {{#if characterCalculate "attributeBase" char._id name}}
<paper-shadow class="card" <paper-shadow class="card"
hero-id="main" {{detailHero name char._id}} hero-id="main" {{detailHero name char._id}}
layout horizontal> layout horizontal>
@@ -152,7 +152,7 @@
disabled={{cantDecrement}}> disabled={{cantDecrement}}>
</paper-icon-button> </paper-icon-button>
</div> </div>
<div>{{char.attributeValue name}}</div> <div>{{characterCalculate "attributeValue" char._id name}}</div>
<!--<div>/{{char.attributeBase name}}</div>--> <!--<div>/{{char.attributeBase name}}</div>-->
</div> </div>
<div class="right clickable" <div class="right clickable"

View File

@@ -96,12 +96,14 @@ Template.features.events({
Template.resource.helpers({ Template.resource.helpers({
cantIncrement: function(){ cantIncrement: function(){
var baseBigger = this.char.attributeValue(this.name) < var value = Characters.calculate.attributeValue(this.char._id, this.name);
this.char.attributeBase(this.name); var base = Characters.calculate.attributeBase(this.char._id, this.name);
var baseBigger = value < base;
return !baseBigger; return !baseBigger;
}, },
cantDecrement: function(){ cantDecrement: function(){
var valuePositive = this.char.attributeValue(this.name) > 0; var value = Characters.calculate.attributeValue(this.char._id, this.name);
var valuePositive = value > 0;
return !valuePositive; return !valuePositive;
}, },
getColor: function(){ getColor: function(){
@@ -115,14 +117,17 @@ Template.resource.helpers({
Template.resource.events({ Template.resource.events({
"tap .resourceUp": function(event){ "tap .resourceUp": function(event){
if (this.char.attributeValue(this.name) < this.char.attributeBase(this.name)){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = 1; modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});
} }
}, },
"tap .resourceDown": function(event){ "tap .resourceDown": function(event){
if (this.char.attributeValue(this.name) > 0){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = -1; modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});

View File

@@ -13,7 +13,7 @@ var getFractionCarried = function(char) {
weight += item.totalWeight(); weight += item.totalWeight();
}); });
//get strength //get strength
var strength = char.attributeValue("strength"); var strength = Characters.calculate.attributeValue(char._id, "strength");
var capacity = strength * 15; var capacity = strength * 15;
return weight / capacity; return weight / capacity;
}; };

View File

@@ -34,9 +34,9 @@
<template name="containerView"> <template name="containerView">
<div layout horizontal wrap center justified> <div layout horizontal wrap center justified>
<table class="summaryTable fullwidth"> <table class="summaryTable fullwidth">
<tr><td>Container</td><td>{{weight}}lbs</td><td>{{longValueString value}}</td></tr> <tr><td>Container</td><td>{{round weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr> <tr><td>Contents</td><td>{{round contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr> <tr class="body2"><td>Total</td><td>{{round totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
</table> </table>
</div> </div>
{{#if description}} {{#if description}}

View File

@@ -9,20 +9,24 @@
<div class="pre-wrap">{{description}}</div> <div class="pre-wrap">{{description}}</div>
{{/if}} {{/if}}
{{else}} {{else}}
<div horizontal layout> {{> experienceEdit}}
<!--Name-->
<paper-input id="experienceNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
<!--Value-->
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
</div>
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="experienceDescriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
{{/baseDialog}} {{/baseDialog}}
{{/with}} {{/with}}
</template> </template>
<template name="experienceEdit">
<div horizontal layout>
<!--Name-->
<paper-input id="experienceNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
<!--Value-->
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
</div>
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="experienceDescriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
</template>

View File

@@ -1,3 +1,7 @@
Template.experienceEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.experienceDialog.helpers({ Template.experienceDialog.helpers({
experience: function(){ experience: function(){
Experiences.findOne(this.experienceId); Experiences.findOne(this.experienceId);
@@ -18,8 +22,10 @@ Template.experienceDialog.events({
); );
GlobalUI.closeDetail(); GlobalUI.closeDetail();
}, },
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors });
"change #experienceNameInput, input #experienceNameInput": function(event){
Template.experienceEdit.events({
"change #experienceNameInput": function(event){
var value = event.currentTarget.value; var value = event.currentTarget.value;
Experiences.update(this._id, {$set: {name: value}}); Experiences.update(this._id, {$set: {name: value}});
}, },

View File

@@ -9,7 +9,7 @@
hero-id="toolbar" {{detailHero}} hero-id="toolbar" {{detailHero}}
layout horizontal center> layout horizontal center>
<div flex>Experience</div> <div flex>Experience</div>
<div >{{experience}} XP</div> <div >{{characterCalculate "experience" _id}} XP</div>
<paper-icon-button class="black54" id="addXP" icon="add"></paper-icon-button> <paper-icon-button class="black54" id="addXP" icon="add"></paper-icon-button>
</div> </div>
<div class="bottom list"> <div class="bottom list">
@@ -45,7 +45,7 @@
layout horizontal center> layout horizontal center>
<div flex> <div flex>
<div class="containerName subhead"> <div class="containerName subhead">
Level {{level}} Level {{characterCalculate "level" _id}}
</div> </div>
{{#if nextLevelXP}} {{#if nextLevelXP}}
<div class="caption"> <div class="caption">

View File

@@ -41,7 +41,7 @@ Template.journal.helpers({
return Levels.find({charId: charId, classId: this._id}, {sort: {value: 1}}); return Levels.find({charId: charId, classId: this._id}, {sort: {value: 1}});
}, },
nextLevelXP: function(){ nextLevelXP: function(){
var currentLevel = this.level(); var currentLevel = Characters.calculate.level(this._id);
if (currentLevel < 20){ if (currentLevel < 20){
return XP_TABLE[currentLevel]; return XP_TABLE[currentLevel];
} }

View File

@@ -9,22 +9,30 @@
</template> </template>
<template name="spellDetails"> <template name="spellDetails">
<div class="caption"> <div class="body2">
Level {{level}} {{school}}, {{preparedString}} Level {{level}} {{school}}, {{preparedString}}
</div> </div>
<div class="vertMargin"> <div style="margin: 16px 0 16px 0;">
{{#if castingTime}}
<div> <div>
<span class="body2">Casting Time: </span><span>{{castingTime}}</span> <span class="body2">Casting Time: </span><span>{{castingTime}}</span>
</div> </div>
{{/if}}
{{#if range}}
<div> <div>
<span class="body2">Range: </span><span>{{range}}</span> <span class="body2">Range: </span><span>{{range}}</span>
</div> </div>
{{/if}}
{{#if getComponents}}
<div> <div>
<span class="body2">Components: </span><span>{{getComponents}}</span> <span class="body2">Components: </span><span>{{getComponents}}</span>
</div> </div>
{{/if}}
{{#if duration}}
<div> <div>
<span class="body2">Duration: </span><span>{{duration}}</span> <span class="body2">Duration: </span><span>{{duration}}</span>
</div> </div>
{{/if}}
</div> </div>
<div class="pre-wrap">{{evaluateString charId description}}</div> <div class="pre-wrap">{{evaluateString charId description}}</div>
{{> attacksViewList charId=charId parentId=_id}} {{> attacksViewList charId=charId parentId=_id}}

View File

@@ -84,32 +84,28 @@ Template.spells.helpers({
}, },
cantCast: function(level, char){ cantCast: function(level, char){
for (var i = level; i <= 9; i++){ for (var i = level; i <= 9; i++){
if (char.attributeValue("level" + i + "SpellSlots") > 0){ if (Characters.calculate.attributeValue(char._id, "level" + i + "SpellSlots") > 0){
return false; return false;
} }
} }
return true; return true;
}, },
baseSlots: function(char){
return char.attributeBase("level" + this.level + "SpellSlots");
},
slots: function(char){
return char.attributeValue("level" + this.level + "SpellSlots");
},
showSlots: function(char){ showSlots: function(char){
return this.level && char.attributeBase("level" + this.level + "SpellSlots"); return this.level && Characters.calculate.attributeBase(
char._id, "level" + this.level + "SpellSlots"
);
}, },
hasSlots: function(){ hasSlots: function(){
for (var i = 1; i <= 9; i += 1){ for (var i = 1; i <= 9; i += 1){
if (this.attributeBase("level" + i + "SpellSlots")){ if (Characters.calculate.attributeBase(this._id, "level" + i + "SpellSlots")){
return true; return true;
} }
} }
return false; return false;
}, },
slotBubbles: function(char){ slotBubbles: function(char){
var baseSlots = char.attributeBase("level" + this.level + "SpellSlots"); var baseSlots = Characters.calculate.attributeBase(char._id, "level" + this.level + "SpellSlots");
var currentSlots = char.attributeValue("level" + this.level + "SpellSlots"); var currentSlots = Characters.calculate.attributeValue(char._id, "level" + this.level + "SpellSlots");
var slotsUsed = baseSlots - currentSlots; var slotsUsed = baseSlots - currentSlots;
var bubbles = []; var bubbles = [];
var i; var i;
@@ -143,15 +139,15 @@ Template.spells.events({
var char = Characters.findOne(this.charId); var char = Characters.findOne(this.charId);
if (event.currentTarget.icon === "radio-button-off"){ if (event.currentTarget.icon === "radio-button-off"){
if ( if (
char.attributeValue(this.attribute) < Characters.calculate.attributeValue(char._id, this.attribute) <
char.attributeBase(this.attribute) Characters.calculate.attributeBase(char._id, this.attribute)
){ ){
modifier = {$inc: {}}; modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = 1; modifier.$inc[this.attribute + ".adjustment"] = 1;
Characters.update(this.charId, modifier, {validate: false}); Characters.update(this.charId, modifier, {validate: false});
} }
} else { } else {
if (char.attributeValue(this.attribute) > 0){ if (Characters.calculate.attributeValue(char._id, this.attribute) > 0){
modifier = {$inc: {}}; modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = -1; modifier.$inc[this.attribute + ".adjustment"] = -1;
Characters.update(this.charId, modifier, {validate: false}); Characters.update(this.charId, modifier, {validate: false});

View File

@@ -4,8 +4,8 @@
layout horizontal> layout horizontal>
<div class="left white-text {{color}}" <div class="left white-text {{color}}"
hero-id="toolbar" {{detailHero ability ../_id}}> hero-id="toolbar" {{detailHero ability ../_id}}>
<div class="display1">{{../attributeValue ability}}</div> <div class="display1">{{characterCalculate "attributeValue" ../_id ability}}</div>
<div class="title">{{../abilityMod ability}}</div> <div class="title">{{abilityMod}}</div>
</div> </div>
<div class="right subhead" layout horizontal center> <div class="right subhead" layout horizontal center>
{{title}} {{title}}

View File

@@ -0,0 +1,9 @@
Template.abilityMiniCard.helpers({
abilityMod: function() {
return signedString(
Characters.calculate.abilityMod(
Template.parentData()._id, this.ability
)
);
}
});

View File

@@ -106,10 +106,8 @@ Template.attributeDialogView.helpers({
return a || b || c; return a || b || c;
}, },
adjustment: function(){ adjustment: function(){
var char = Characters.findOne(this.charId); var value = Characters.calculate.attributeValue(this.charId, this.statName);
if (!char) return; var base = Characters.calculate.attributeBase(this.charId, this.statName);
var value = char.attributeValue(this.statName);
var base = char.attributeBase(this.statName);
return value - base; return value - base;
}, },
baseEffects: function(){ baseEffects: function(){
@@ -138,14 +136,10 @@ Template.attributeDialogView.helpers({
); );
}, },
attributeBase: function(){ attributeBase: function(){
var char = Characters.findOne(this.charId); return Characters.calculate.attributeBase(this.charId, this.statName);
if (!char) throw "character is " + char;
return char.attributeBase(this.statName);
}, },
attributeValue: function() { attributeValue: function() {
var char = Characters.findOne(this.charId); return Characters.calculate.attributeValue(this.charId, this.statName);
if (!char) throw "character is " + char;
return char.attributeValue(this.statName);
}, },
sourceName: function(){ sourceName: function(){
if (this.parent.group === "racial"){ if (this.parent.group === "racial"){

View File

@@ -11,11 +11,11 @@
<div class="right" flex layout vertical center-justified style="min-width: 180px;"> <div class="right" flex layout vertical center-justified style="min-width: 180px;">
<div layout horizontal> <div layout horizontal>
<paper-diff-slider id="hitPointSlider" <paper-diff-slider id="hitPointSlider"
value={{attributeValue "hitPoints"}} value={{characterCalculate "attributeValue" _id "hitPoints"}}
max={{attributeBase "hitPoints"}} max={{characterCalculate "attributeBase" _id "hitPoints"}}
editable pin editable pin
role="slider" role="slider">
></paper-diff-slider> </paper-diff-slider>
</div> </div>
{{#each tempHitPoints}} {{#each tempHitPoints}}
<div> <div>
@@ -35,9 +35,21 @@
</div> </div>
{{/each}} {{/each}}
<div class="caption"> <div class="caption">
{{#if multipliers.immunities.length}} <div>Immune: {{#each multipliers.immunities}} {{name}} {{/each}}</div>{{/if}} {{#if multipliers.immunities.length}}
{{#if multipliers.resistances.length}}<div>Resistance: {{#each multipliers.resistances}} {{name}} {{/each}}</div>{{/if}} <div>
{{#if multipliers.weaknesses.length}} <div>Weakness: {{#each multipliers.weaknesses}} {{name}} {{/each}}</div>{{/if}} Immune: {{#each multipliers.immunities}} {{name}} {{/each}}
</div>
{{/if}}
{{#if multipliers.resistances.length}}
<div>
Resistance: {{#each multipliers.resistances}} {{name}} {{/each}}
</div>
{{/if}}
{{#if multipliers.weaknesses.length}}
<div>
Weakness: {{#each multipliers.weaknesses}} {{name}} {{/each}}
</div>
{{/if}}
</div> </div>
{{#if showDeathSave}} {{#if showDeathSave}}
{{#with deathSaveObject}} {{#with deathSaveObject}}

View File

@@ -3,7 +3,7 @@ Template.healthCard.helpers({
return TemporaryHitPoints.find({charId: this._id}); return TemporaryHitPoints.find({charId: this._id});
}, },
showDeathSave: function(){ showDeathSave: function(){
return this.attributeValue("hitPoints") <= 0; return Characters.calculate.attributeValue(this._id, "hitPoints") <= 0;
}, },
deathSaveObject: function(){ deathSaveObject: function(){
var char = Characters.findOne(this._id, {fields: {deathSave: 1}}); var char = Characters.findOne(this._id, {fields: {deathSave: 1}});
@@ -27,21 +27,20 @@ Template.healthCard.helpers({
return this.fail >= 3; return this.fail >= 3;
}, },
multipliers: function(){ multipliers: function(){
var char = Characters.findOne(this._id, {fields: {_id: 1}});
var multipliers = [ var multipliers = [
{name: "Acid", value: char.attributeValue("acidMultiplier", 1)}, {name: "Acid", value: Characters.calculate.attributeValue(this._id, "acidMultiplier")},
{name: "Bludgeoning", value: char.attributeValue("bludgeoningMultiplier", 1)}, {name: "Bludgeoning", value: Characters.calculate.attributeValue(this._id, "bludgeoningMultiplier")},
{name: "Cold", value: char.attributeValue("coldMultiplier", 1)}, {name: "Cold", value: Characters.calculate.attributeValue(this._id, "coldMultiplier")},
{name: "Fire", value: char.attributeValue("fireMultiplier", 1)}, {name: "Fire", value: Characters.calculate.attributeValue(this._id, "fireMultiplier")},
{name: "Force", value: char.attributeValue("forceMultiplier", 1)}, {name: "Force", value: Characters.calculate.attributeValue(this._id, "forceMultiplier")},
{name: "Lightning", value: char.attributeValue("lightningMultiplier", 1)}, {name: "Lightning", value: Characters.calculate.attributeValue(this._id, "lightningMultiplier")},
{name: "Necrotic", value: char.attributeValue("necroticMultiplier", 1)}, {name: "Necrotic", value: Characters.calculate.attributeValue(this._id, "necroticMultiplier")},
{name: "Piercing", value: char.attributeValue("piercingMultiplier", 1)}, {name: "Piercing", value: Characters.calculate.attributeValue(this._id, "piercingMultiplier")},
{name: "Poison", value: char.attributeValue("poisonMultiplier", 1)}, {name: "Poison", value: Characters.calculate.attributeValue(this._id, "poisonMultiplier")},
{name: "Psychic", value: char.attributeValue("psychicMultiplier", 1)}, {name: "Psychic", value: Characters.calculate.attributeValue(this._id, "psychicMultiplier")},
{name: "Radiant", value: char.attributeValue("radiantMultiplier", 1)}, {name: "Radiant", value: Characters.calculate.attributeValue(this._id, "radiantMultiplier")},
{name: "Slashing", value: char.attributeValue("slashingMultiplier", 1)}, {name: "Slashing", value: Characters.calculate.attributeValue(this._id, "slashingMultiplier")},
{name: "Thunder", value: char.attributeValue("thunderMultiplier", 1)}, {name: "Thunder", value: Characters.calculate.attributeValue(this._id, "thunderMultiplier")},
]; ];
multipliers = _.groupBy(multipliers, "value"); multipliers = _.groupBy(multipliers, "value");
return { return {
@@ -55,7 +54,8 @@ Template.healthCard.helpers({
Template.healthCard.events({ Template.healthCard.events({
"change #hitPointSlider": function(event){ "change #hitPointSlider": function(event){
var value = event.currentTarget.value; var value = event.currentTarget.value;
var adjustment = value - this.attributeBase("hitPoints"); var base = Characters.calculate.attributeBase(this._id, "hitPoints")
var adjustment = value - base;
Characters.update(this._id, {$set: {"hitPoints.adjustment": adjustment}}); Characters.update(this._id, {$set: {"hitPoints.adjustment": adjustment}});
//reset the death saves if we are gaining HP //reset the death saves if we are gaining HP
if (value > 0) if (value > 0)

View File

@@ -1,5 +1,5 @@
<template name="hitDice"> <template name="hitDice">
{{#if ../attributeBase name}} {{#if characterCalculate "attributeBase" ../_id name}}
<paper-shadow class="card hit-dice" hero-id="main" <paper-shadow class="card hit-dice" hero-id="main"
{{detailHero name ../_id}} {{detailHero name ../_id}}
layout horizontal> layout horizontal>
@@ -18,10 +18,10 @@
</div> </div>
<div class="resourceValue" layout vertical center> <div class="resourceValue" layout vertical center>
<div> <div>
{{../attributeValue name}} {{characterCalculate "attributeValue" ../_id name}}
</div> </div>
<div class="title white-text"> <div class="title white-text">
d{{diceNum}} {{../abilityMod "constitution"}} d{{diceNum}} {{characterCalculate "abilityMod" ../_id "constitution"}}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,28 @@
Template.hitDice.helpers({ Template.hitDice.helpers({
cantIncrement: function(){ cantIncrement: function(){
var valueSmallerThanBase = this.char.attributeValue(this.name) < var value = Characters.calculate.attributeValue(this.char._id, this.name);
this.char.attributeBase(this.name); var base = Characters.calculate.attributeBase(this.char._id, this.name);
return !valueSmallerThanBase; return value >= base;
}, },
cantDecrement: function(){ cantDecrement: function(){
var valuePositive = this.char.attributeValue(this.name) > 0; var value = Characters.calculate.attributeValue(this.char._id, this.name);
return !valuePositive; return value <= 0;
}, },
}); });
Template.hitDice.events({ Template.hitDice.events({
"tap .resourceUp": function(event){ "tap .resourceUp": function(event){
if (this.char.attributeValue(this.name) < this.char.attributeBase(this.name)){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = 1; modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});
} }
}, },
"tap .resourceDown": function(event){ "tap .resourceDown": function(event){
if (this.char.attributeValue(this.name) > 0){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = -1; modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});

View File

@@ -1,4 +1,4 @@
<!-- needs name, char, and skillName --> <!-- needs name, charId, and skillName -->
<template name="skillDialog"> <template name="skillDialog">
{{#baseDialog title=name class=color hideEdit=true}} {{#baseDialog title=name class=color hideEdit=true}}
{{> skillDialogView}} {{> skillDialogView}}
@@ -8,7 +8,7 @@
<template name="skillDialogView"> <template name="skillDialogView">
<div layout vertical center> <div layout vertical center>
<div class="display2"> <div class="display2">
{{char.skillMod skillName}} {{characterCalculate "skillMod" charId skillName}}
</div> </div>
<div class="subhead"> <div class="subhead">
<core-icon icon="{{profIcon}}" class="black54"></core-icon> <core-icon icon="{{profIcon}}" class="black54"></core-icon>
@@ -25,9 +25,9 @@
<table class="summaryTable"> <table class="summaryTable">
<tr> <tr>
<td>{{abilityName}}</td> <td>{{abilityName}}</td>
<td>{{char.abilityMod ability}}</td> <td>{{characterCalculate "abilityMod" charId ability}}</td>
</tr> </tr>
{{#if char.proficiency skillName}} {{#if characterCalculate "proficiency" charId skillName}}
<tr> <tr>
<td>{{proficiencyValue}}</td> <td>{{proficiencyValue}}</td>
<td>{{signedString profBonus}}</td> <td>{{signedString profBonus}}</td>
@@ -59,7 +59,7 @@
{{/each}} {{/each}}
<tr class="body2"> <tr class="body2">
<td>Total</td> <td>Total</td>
<td>{{char.skillMod skillName}}</td> <td>{{characterCalculate "skillMod" charId skillName}}</td>
</tr> </tr>
</table> </table>

View File

@@ -106,9 +106,7 @@ Template.skillDialogView.helpers({
return a || b || c; return a || b || c;
}, },
profIcon: function(){ profIcon: function(){
var char = Characters.findOne(this.charId); var prof = Characters.calculate.proficiency(this.charId, this.skillName);
if (!char) return;
var prof = char.proficiency(this.skillName);
if (prof > 0 && prof < 1) return "image:brightness-2"; if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1"; if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album"; if (prof > 1) return "av:album";
@@ -123,13 +121,13 @@ Template.skillDialogView.helpers({
profBonus: function(){ profBonus: function(){
var char = Characters.findOne(this.charId); var char = Characters.findOne(this.charId);
if (!char) return; if (!char) return;
return char.proficiency(this.skillName) * var prof = Characters.calculate.proficiency(this.charId, this.skillName);
char.attributeValue("proficiencyBonus"); var proficiencyBonus =
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
return prof * proficiencyBonus;
}, },
proficiencyValue: function(){ proficiencyValue: function(){
var char = Characters.findOne(this.charId); var prof = Characters.calculate.proficiency(this.charId, this.skillName);
if (!char) return;
var prof = char.proficiency(this.skillName);
if (prof == 0.5) return "Half Proficiency"; if (prof == 0.5) return "Half Proficiency";
if (prof == 1) return "Proficient"; if (prof == 1) return "Proficient";
if (prof == 2) return "Double Proficiency"; if (prof == 2) return "Double Proficiency";
@@ -199,22 +197,15 @@ Template.skillDialogView.helpers({
return skill.ability; return skill.ability;
}, },
abilityName: function(){ abilityName: function(){
var opts = {fields: {}}; var skill = Characters.calculate.getField(this.charId, this.skillName);
opts.fields[this.skillName] = 1;
var char = Characters.findOne(this.charId, opts);
if (!char) return;
var skill = char[this.skillName];
if (!skill) return; if (!skill) return;
var ability = skill.ability; var ability = skill.ability;
return abilities[ability] && abilities[ability].name; return abilities[ability] && abilities[ability].name;
}, },
char: function(){
return Characters.findOne(this.charId, {fields:{_id: 1}});
},
sourceName: function(){ sourceName: function(){
if (this.parent.collection === "Characters"){ if (this.parent.collection === "Characters"){
if (this.parent.group === "racial"){ if (this.parent.group === "racial"){
return Characters.findOne(this.charId, {fields:{race: 1}}).race || "Race"; return Characters.calculate.getField(this.charId, "race") || "Race";
} }
if (this.parent.group === "background"){ if (this.parent.group === "background"){
return "Background"; return "Background";

View File

@@ -8,7 +8,9 @@
{{#if failSkill}} {{#if failSkill}}
<div class="fail skill-mod">fail</div> <div class="fail skill-mod">fail</div>
{{else}} {{else}}
<div class="{{advantage}} skill-mod">{{../skillMod skill}}</div> <div class="{{advantage}} skill-mod">
{{skillMod}}
</div>
{{/if}} {{/if}}
<div flex> <div flex>
{{name}} {{name}}
@@ -16,7 +18,7 @@
* *
{{/if}} {{/if}}
{{#if showPassive}} {{#if showPassive}}
({{../passiveSkill skill}}) ({{characterCalculate "passiveSkill" ../_id skill}})
{{/if}} {{/if}}
</div> </div>
</div> </div>

View File

@@ -1,13 +1,21 @@
Template.skillRow.helpers({ Template.skillRow.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.skill
)
);
},
profIcon: function(){ profIcon: function(){
var prof = Template.parentData(1).proficiency(this.skill); var charId = Template.parentData()._id;
var prof = Characters.calculate.proficiency(charId, this.skill);
if (prof > 0 && prof < 1) return "image:brightness-2"; if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1"; if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album"; if (prof > 1) return "av:album";
return "radio-button-off"; return "radio-button-off";
}, },
failSkill: function(){ failSkill: function(){
var charId = Template.parentData(1)._id; var charId = Template.parentData()._id;
return Effects.find({ return Effects.find({
charId: charId, charId: charId,
stat: this.skill, stat: this.skill,
@@ -16,12 +24,13 @@ Template.skillRow.helpers({
}).count(); }).count();
}, },
advantage: function(){ advantage: function(){
var advantage = Template.parentData(1).advantage(this.skill); var charId = Template.parentData()._id;
var advantage = Characters.calculate.advantage(charId, this.skill);
if (advantage > 0) return "advantage"; if (advantage > 0) return "advantage";
if (advantage < 0) return "disadvantage"; if (advantage < 0) return "disadvantage";
}, },
conditionalCount: function(){ conditionalCount: function(){
var charId = Template.parentData(1)._id; var charId = Template.parentData()._id;
return Effects.find({ return Effects.find({
charId: charId, charId: charId,
stat: this.skill, stat: this.skill,

View File

@@ -75,9 +75,9 @@
<div class="left display1 white-text {{color}}" <div class="left display1 white-text {{color}}"
hero-id="toolbar" {{detailHero stat ../_id}}> hero-id="toolbar" {{detailHero stat ../_id}}>
{{#if isSkill}} {{#if isSkill}}
{{../skillMod stat}} {{prefix}}{{skillMod}}
{{else}} {{else}}
{{prefix}}{{../attributeValue stat}} {{prefix}}{{characterCalculate "attributeValue" ../_id stat}}
{{/if}} {{/if}}
</div> </div>
<div class="right subhead" flex horizontal layout center> <div class="right subhead" flex horizontal layout center>

View File

@@ -67,6 +67,12 @@ Template.stats.events({
}, },
}); });
Template.stats.helpers({ Template.statCard.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.stat
)
);
},
}); });

View File

@@ -13,22 +13,22 @@
role="button" role="button"
tabindex="0" tabindex="0"
icon="delete" icon="delete"
aria-label="Delete Feature" aria-label="Delete Feature">
noink></paper-icon-button> </paper-icon-button>
{{/unless}} {{/unless}}
{{#unless hideColor}} {{#unless hideColor}}
{{> colorDropdown}} {{> colorDropdown}}
{{/unless}} {{/unless}}
<paper-icon-button id="doneEditingButton" <paper-icon-button id="doneEditingButton"
icon="done" icon="done"
aria-label="Delete Feature" aria-label="Delete Feature">
noink></paper-icon-button> </paper-icon-button>
{{else}} {{else}}
{{#if showEdit}} {{#if showEdit}}
<paper-icon-button id="editButton" <paper-icon-button id="editButton"
icon="create" icon="create"
aria-label="Delete Feature" aria-label="Delete Feature">
noink></paper-icon-button> </paper-icon-button>
{{/if}} {{/if}}
{{/if}} {{/if}}
</div> </div>

View File

@@ -1,11 +1,10 @@
//evaluates a calculation string //evaluates a calculation string
evaluate = function(charId, string){ evaluate = function(charId, string){
if (!string) return string; if (!string) return string;
var char = Characters.findOne(charId, {fields: {_id: 1}});
string = string.replace(/\b[a-z]+\b/gi, function(sub){ string = string.replace(/\b[a-z]+\b/gi, function(sub){
//fields //fields
if (Schemas.Character.schema(sub)){ if (Schemas.Character.schema(sub)){
return char.fieldValue(sub); return Characters.calculate.fieldValue(charId, sub);
} }
//ability modifiers //ability modifiers
var abilityMods = [ var abilityMods = [
@@ -19,7 +18,7 @@ evaluate = function(charId, string){
if (_.contains(abilityMods, sub)){ if (_.contains(abilityMods, sub)){
var slice = sub.slice(0, -3); var slice = sub.slice(0, -3);
try { try {
return char.abilityMod(slice); return Characters.calculate.abilityMod(charId, slice);
} catch (e){ } catch (e){
return sub; return sub;
} }
@@ -33,7 +32,7 @@ evaluate = function(charId, string){
} }
//character level //character level
if (sub.toUpperCase() === "LEVEL"){ if (sub.toUpperCase() === "LEVEL"){
return char.level(); return Characters.calculate.level(charId);
} }
return sub; return sub;
}); });
@@ -41,7 +40,6 @@ evaluate = function(charId, string){
var result = math.eval(string); var result = math.eval(string);
return result; return result;
} catch (e){ } catch (e){
console.log("Failed to evaluate ", string);
return string; return string;
} }
}; };

View File

@@ -1,4 +1,5 @@
preventLoop = function(inputFunction){ preventLoop = function(inputFunction){
var self = this;
if (!_.isFunction(inputFunction)){ if (!_.isFunction(inputFunction)){
throw new Meteor.Error( throw new Meteor.Error(
"Not a function", "Not a function",
@@ -9,23 +10,26 @@ preventLoop = function(inputFunction){
//if we try to visit the same argument twice before resolving its value //if we try to visit the same argument twice before resolving its value
//we are in a dependency loop and need to GTFO //we are in a dependency loop and need to GTFO
var visitedArgs = []; var visitedArgs = [];
return function(argument){ return function(){
var value; var result;
var hash = _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
//we're still evaluating this attribute, must be in a loop //we're still evaluating this attribute, must be in a loop
if (_.contains(visitedArgs, argument)) { if (_.contains(visitedArgs, hash)) {
console.warn("dependency loop detected"); console.warn("dependency loop detected");
return NaN; return NaN;
} else { } else {
//push this skill to the list of visited skills //push this hash to the list of visited hashes
//we can't visit it again unless it returns first //we can't visit it again unless it returns first
visitedArgs.push(argument); visitedArgs.push(hash);
} }
try { try {
value = inputFunction.call(this, argument); result = inputFunction.apply(this, arguments);
} finally{ } finally{
//this argument returns or fails, pull it from the array //this hash returns or fails, pull it from the array
visitedArgs = _.without(visitedArgs, argument); visitedArgs = _.without(visitedArgs, hash);
} }
return value; return result;
}; };
}; };

View File

@@ -0,0 +1,45 @@
Tracker.memoize = function(func, hasher){
var memoize = function(key) {
var cache = memoize.cache;
var address = "" + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address)) {
cache[address] = new CacheObject(func, address, arguments, cache, this);
}
return cache[address].get();
};
memoize.cache = {};
return memoize;
};
function CacheObject(func, address, args, cache, context){
var self = this;
self.currentValue = null;
self.dep = new Tracker.Dependency();
//spawn a new autorun that keeps the value up-to-date
Tracker.nonreactive(function() {
Tracker.autorun(function(computation) {
//if this isn't the first run and nobody is listening,
//delete itself from cache and stop the computation
if (!computation.firstRun && !self.dep.hasDependents()){
computation.stop();
delete cache[address];
return;
}
//call the expensive function
var newValue = func.apply(context, args);
//if the value changed, store the new value
if (self.currentValue !== newValue){
self.currentValue = newValue;
//tell the dependents that we've changed
self.dep.changed();
}
});
});
}
CacheObject.prototype.get = function() {
//if there is an active computation, track dependents
if (Tracker.active) this.dep.depend();
return this.currentValue;
};

View File

@@ -98,9 +98,9 @@ trackEncumbranceConditions = function(charId, templateInstance) {
}); });
var character = Characters.findOne( var character = Characters.findOne(
charId, charId,
{fields: {strength: 1, "settings": 1}} {fields: {"settings": 1}}
); );
var strength = character.attributeValue("strength"); var strength = Characters.calculate.attributeValue(charId, "strength");
var give = function(condition) { var give = function(condition) {
Meteor.call("giveCondition", charId, condition); Meteor.call("giveCondition", charId, condition);
}; };

View File

@@ -176,3 +176,20 @@ ChangeLogs.insert({
"Added a move item dialog to mobile devices, but it can't be accessed yet, because long-presses are broken", "Added a move item dialog to mobile devices, but it can't be accessed yet, because long-presses are broken",
], ],
}); });
ChangeLogs.insert({
version: "0.6.0",
changes: [
"Big performance improvements: loading characters that you've viewed recently and changing effects on characters should be much faster",
"Spell dialogs no longer show their casting time, range, etc. if those fields aren't filled in",
"The settings dialog now has a done button so it can be closed on small devices",
"Container dialogs now have the weight summaries rounded down properly",
],
});
ChangeLogs.insert({
version: "0.6.1",
changes: [
"Experience dialogs should update their edit-mode inputs properly now",
],
});