Duplicate character helpers with memoized functions not attached to characters
This commit is contained in:
@@ -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},
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user