Duplicate character helpers with memoized functions not attached to characters

This commit is contained in:
Stefan Zermatten
2015-06-18 13:34:59 +02:00
parent d5680ebf8a
commit a034cbf30e
2 changed files with 247 additions and 62 deletions

View File

@@ -190,79 +190,252 @@ var attributeBase = function(charId, statName){
check(statName, String);
//if it's a damage multiplier, we treat it specially
if (_.contains(DAMAGE_MULTIPLIERS, statName)){
var effects = Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "mul"}
).fetch();
var resistCount = 0;
var vulnCount = 0;
var multiplierEvaluationFail = false;
_.each(effects, function(effect){
var val = evaluateEffect(charId, effect);
if (val === 0.5){ //resistance
resistCount += 1;
} else if (val === 2){ //vulnerability
vulnCount += 1;
} else if (val === 0){ //imunity
return 0; //imunity is absolute
} else {
multiplierEvaluationFail = true;
}
});
if (multiplierEvaluationFail){
//we can't work it out correctly, set the value to 1
//and try work it out using regular maths below
value = 1;
} else if (resistCount && !vulnCount){
var invulnerabilityCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0,
}).count();
if (invulnerabilityCount) return 0;
var resistCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0.5,
}).count();
var vulnCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 2,
}).count();
if (!resistCount && !vulnCount){
return 1;
} else if (resistCount && !vulnCount){
return 0.5;
} else if (!resistCount && vulnCount){
} else if (!resistCount && vulnCount){
return 2;
} else {
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;
//start with the highest base value
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "base"}
).forEach(function(effect){
var efv = evaluateEffect(charId, effect);
if (efv > value){
value = efv;
Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "base"){
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
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "add"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
var result = (base + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
//multiply all the mul values
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "mul"}
).forEach(function(effect){
value *= evaluateEffect(charId, effect);
});
return result;
};
//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;
if (Meteor.isClient) {
Template.registerHelper("charCalculate", function(func, charId, input) {
return Characters.calculate[func](charId, input);
});
}
//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;
//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;
}, "");
});
return value;
};
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(charId, {fields: fieldSelector});
var field = char[fieldName];
if (field === undefined){
throw new Meteor.Error(
"getField failed",
"getField could not find field " +
fieldName +
" in character " +
char._id
);
}
return field;
},
fieldValue: function(charId, fieldName) {
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
"Character's schema does not contain a field called: " + fieldName
);
}
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return Characters.calculate.skillMod(charId, fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return Characters.calculate.attributeValue(charId, fieldName);
}
//fall back to just returning the field itself
return Characters.calculate.getField(charId, fieldName);
},
attributeValue: memoize(function(charId, attributeName){
var attribute = Characters.calculate.getField(charId, attributeName);
//base value
var value = Characters.calculate.attributeBase(attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
}),
attributeBase: preventLoop(memoize(function(charId, attributeName){
return attributeBase(charId, attributeName);
})),
skillMod: preventLoop(memoize(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
//get the final value of the ability score
var ability = Characters.calculate.attributeValue(charId, skill.ability);
//base modifier
var mod = +getMod(ability);
//multiply proficiency bonus by largest value in proficiency array
var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier
mod += prof * Characters.calculate.attributeValue(charId, "proficiencyBonus");
//apply all effects
var value;
var add = 0;
var mul = 1;
var min = Math.NEGATIVE_INFINITY;
var max = Math.POSITIVE_INFINITY;
Effects.find({
charId: charId,
stat: skillName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
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;
}
});
var result = (mod + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
return result;
})),
proficiency: memoize(function(charId, skillName){
//return largest value in proficiency array
var prof = Proficiencies.findOne(
{charId: charId, name: skillName, enabled: true},
{sort: {value: -1}}
);
return prof && prof.value;
}),
passiveSkill: memoize(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
var mod = +Characters.calculate.skillMod(charId, skillName);
var value = 10 + mod;
Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
var advantage = Characters.calculate.advantage(charId, skillName);
value += 5 * advantage;
return value;
}),
advantage: memoize(function(charId, skillName){
var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"}
).count();
var disadvantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "disadvantage"}
).count();
if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1;
return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
},
passiveAbility: function(charId, attribute){
var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return 10 + mod;
},
xpLevel: function(charId){
var xp = Characters.calculate.experience(charId);
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
return i;
}
}
if (xp > 355000) return 20;
return 0;
},
level: memoize(function(charId){
var level = 0;
Classes.find({charId: charId}).forEach(function(cls){
level += cls.level;
});
return level;
}),
experience: memoize(function(charId){
var xp = 0;
Experiences.find(
{charId: charId},
{fields: {value: 1}}
).forEach(function(e){
xp += e.value;
});
return xp;
}),
};
var depreciated = function() {
var err = new Error();
console.log("this function has been superceeded", {stacktrace: err.stack});
};
//functions and calculated values.
@@ -272,6 +445,7 @@ Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
depreciated();
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector});
@@ -289,6 +463,7 @@ Characters.helpers({
},
//returns the value of a field
fieldValue : function(fieldName){
depreciated();
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
@@ -309,6 +484,7 @@ Characters.helpers({
},
attributeValue: function(attributeName){
depreciated();
var charId = this._id;
var attribute = this.getField(attributeName);
//base value
@@ -319,12 +495,14 @@ Characters.helpers({
},
attributeBase: preventLoop(function(attributeName){
depreciated();
var charId = this._id;
//base value
return attributeBase(charId, attributeName);
}),
skillMod: preventLoop(function(skillName){
depreciated();
var charId = this._id;
var skill = this.getField(skillName);
//get the final value of the ability score
@@ -362,6 +540,7 @@ Characters.helpers({
}),
proficiency: function(skillName){
depreciated();
var charId = this._id;
//return largest value in proficiency array
var prof = 0;
@@ -377,6 +556,7 @@ Characters.helpers({
},
passiveSkill: function(skillName){
depreciated();
if (_.isString(skillName)){
var skill = this.getField(skillName);
}
@@ -393,6 +573,7 @@ Characters.helpers({
},
advantage: function(skillName){
depreciated();
var charId = this._id;
var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"}
@@ -406,15 +587,18 @@ Characters.helpers({
},
abilityMod: function(attribute){
depreciated();
return signedString(getMod(this.attributeValue(attribute)));
},
passiveAbility: function(attribute){
depreciated();
var mod = +getMod(this.attributeValue(attribute));
return 10 + mod;
},
xpLevel: function(){
depreciated();
var xp = this.experience();
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
@@ -426,6 +610,7 @@ Characters.helpers({
},
level: function(){
depreciated();
var level = 0;
Classes.find({charId: this._id}).forEach(function(cls){
level += cls.level;
@@ -434,6 +619,7 @@ Characters.helpers({
},
experience: function(){
depreciated();
var xp = 0;
Experiences.find(
{charId: this._id},

View File

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