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); 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 if (Meteor.isClient) {
Effects.find( Template.registerHelper("charCalculate", function(func, charId, input) {
{charId: charId, stat: statName, enabled: true, operation: "min"} return Characters.calculate[func](charId, input);
).forEach(function(effect){
var min = evaluateEffect(charId, effect);
value = value > min ? value : min;
}); });
}
//ensure value is <= all maxes //create a local memoize with a argument concatenating hash function
Effects.find( var memoize = function(f) {
{charId: charId, stat: statName, enabled: true, operation: "max"} return Tracker.memoize(f, function() {
).forEach(function(effect){ return _.reduce(arguments, function(memo, arg) {
var max = evaluateEffect(charId, effect); return memo + arg;
value = value < max ? value : max; }, "");
}); });
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. //functions and calculated values.
@@ -272,6 +445,7 @@ Characters.helpers({
//returns the value stored in the field requested //returns the value stored in the field requested
//will set up dependencies on just that field //will set up dependencies on just that field
getField : function(fieldName){ getField : function(fieldName){
depreciated();
var fieldSelector = {}; var fieldSelector = {};
fieldSelector[fieldName] = 1; fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector}); var char = Characters.findOne(this._id, {fields: fieldSelector});
@@ -289,6 +463,7 @@ Characters.helpers({
}, },
//returns the value of a field //returns the value of a field
fieldValue : function(fieldName){ fieldValue : function(fieldName){
depreciated();
if (!Schemas.Character.schema(fieldName)){ if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error( throw new Meteor.Error(
"Field not found", "Field not found",
@@ -309,6 +484,7 @@ Characters.helpers({
}, },
attributeValue: function(attributeName){ attributeValue: function(attributeName){
depreciated();
var charId = this._id; var charId = this._id;
var attribute = this.getField(attributeName); var attribute = this.getField(attributeName);
//base value //base value
@@ -319,12 +495,14 @@ Characters.helpers({
}, },
attributeBase: preventLoop(function(attributeName){ attributeBase: preventLoop(function(attributeName){
depreciated();
var charId = this._id; var charId = this._id;
//base value //base value
return attributeBase(charId, attributeName); return attributeBase(charId, attributeName);
}), }),
skillMod: preventLoop(function(skillName){ skillMod: preventLoop(function(skillName){
depreciated();
var charId = this._id; var charId = this._id;
var skill = this.getField(skillName); var skill = this.getField(skillName);
//get the final value of the ability score //get the final value of the ability score
@@ -362,6 +540,7 @@ Characters.helpers({
}), }),
proficiency: function(skillName){ proficiency: function(skillName){
depreciated();
var charId = this._id; var charId = this._id;
//return largest value in proficiency array //return largest value in proficiency array
var prof = 0; var prof = 0;
@@ -377,6 +556,7 @@ Characters.helpers({
}, },
passiveSkill: function(skillName){ passiveSkill: function(skillName){
depreciated();
if (_.isString(skillName)){ if (_.isString(skillName)){
var skill = this.getField(skillName); var skill = this.getField(skillName);
} }
@@ -393,6 +573,7 @@ Characters.helpers({
}, },
advantage: function(skillName){ advantage: function(skillName){
depreciated();
var charId = this._id; 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"}
@@ -406,15 +587,18 @@ Characters.helpers({
}, },
abilityMod: function(attribute){ abilityMod: function(attribute){
depreciated();
return signedString(getMod(this.attributeValue(attribute))); return signedString(getMod(this.attributeValue(attribute)));
}, },
passiveAbility: function(attribute){ passiveAbility: function(attribute){
depreciated();
var mod = +getMod(this.attributeValue(attribute)); var mod = +getMod(this.attributeValue(attribute));
return 10 + mod; return 10 + mod;
}, },
xpLevel: function(){ xpLevel: function(){
depreciated();
var xp = this.experience(); 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]){
@@ -426,6 +610,7 @@ Characters.helpers({
}, },
level: function(){ level: function(){
depreciated();
var level = 0; var level = 0;
Classes.find({charId: this._id}).forEach(function(cls){ Classes.find({charId: this._id}).forEach(function(cls){
level += cls.level; level += cls.level;
@@ -434,6 +619,7 @@ Characters.helpers({
}, },
experience: function(){ experience: function(){
depreciated();
var xp = 0; var xp = 0;
Experiences.find( Experiences.find(
{charId: this._id}, {charId: this._id},

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