Added effect math tests
This commit is contained in:
2
.codio
2
.codio
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
// Run button configuration
|
// Run button configuration
|
||||||
"commands": {
|
"commands": {
|
||||||
"Run Meteor": "ROOT_URL=http://period-sheriff-3000.codio.io\ncd rpg-docs \n meteor run"
|
"Run Meteor": "cd rpg-docs \n ROOT_URL=http://period-sheriff.codio.io:3000 meteor"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Preview button configuration
|
// Preview button configuration
|
||||||
|
|||||||
@@ -257,87 +257,46 @@ Characters.helpers({
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
|
||||||
attributeBase: (function(){
|
attributeBase: preventLoop(function(attributeName){
|
||||||
//store a private array of attributes we've visited without returning
|
var charId = this._id;
|
||||||
//if we try to visit the same attribute twice before resolving its value
|
//base value
|
||||||
//we are in a dependency loop and need to GTFO
|
return attributeBase(charId, attributeName);
|
||||||
var visitedAttributes = [];
|
}),
|
||||||
return function(attributeName){
|
|
||||||
check(attributeName, String);
|
|
||||||
//we're still evaluating this attribute, must be in a loop
|
|
||||||
if(_.contains(visitedAttributes, attributeName)) {
|
|
||||||
console.log("dependency loop detected");
|
|
||||||
return NaN;
|
|
||||||
}
|
|
||||||
//push this attribute to the list of visited attributes
|
|
||||||
//we can't visit it again unless it returns first
|
|
||||||
visitedAttributes.push(attributeName);
|
|
||||||
try{
|
|
||||||
var charId = this._id;
|
|
||||||
//base value
|
|
||||||
var value = attributeBase(charId, attributeName);
|
|
||||||
}finally{
|
|
||||||
//this attribute returns or fails, pull it from the array, we may visit it again safely
|
|
||||||
visitedAttributes = _.without(visitedAttributes, attributeName);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
|
|
||||||
skillMod: (function(){
|
skillMod: preventLoop(function(skillName){
|
||||||
//store a private array of skills we've visited without returning
|
var charId = this._id;
|
||||||
//if we try to visit the same skill twice before resolving its value
|
var skill = this.getField(skillName);
|
||||||
//we are in a dependency loop and need to GTFO
|
//get the final value of the ability score
|
||||||
var visitedSkills = [];
|
var ability = this.attributeValue(skill.ability);
|
||||||
return function(skillName){
|
|
||||||
check(skillName, String);
|
|
||||||
//we're still evaluating this attribute, must be in a loop
|
|
||||||
if(_.contains(visitedSkills, skillName)) {
|
|
||||||
console.log("dependency loop detected");
|
|
||||||
return NaN;
|
|
||||||
}
|
|
||||||
//push this skill to the list of visited skills
|
|
||||||
//we can't visit it again unless it returns first
|
|
||||||
visitedSkills.push(skillName);
|
|
||||||
try{
|
|
||||||
var charId = this._id;
|
|
||||||
skill = this.getField(skillName);
|
|
||||||
//get the final value of the ability score
|
|
||||||
var ability = this.attributeValue(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 = this.proficiency(skillName);
|
||||||
|
|
||||||
//add multiplied proficiency bonus to modifier
|
//add multiplied proficiency bonus to modifier
|
||||||
mod += prof * this.attributeValue("proficiencyBonus");
|
mod += prof * this.attributeValue("proficiencyBonus");
|
||||||
Effects.find({charId: charId, stat: skillName, enabled: true}).forEach(function(effect){
|
|
||||||
switch(effect.operation) {
|
//apply all effects
|
||||||
case "add":
|
var rawEffects = Effects.find({charId: charId, stat: skillName, enabled: true}).fetch();
|
||||||
mod += evaluateEffect(charId, effect);
|
var effects = _.groupBy(rawEffects, "operation");
|
||||||
break;
|
_.forEach(effects.add, function(effect){
|
||||||
case "mul":
|
mod += evaluateEffect(charId, effect);
|
||||||
mod *= evaluateEffect(charId, effect);
|
});
|
||||||
break;
|
_.forEach(effects.mul, function(effect){
|
||||||
case "min":
|
mod *= evaluateEffect(charId, effect);
|
||||||
var min = evaluateEffect(charId, effect);
|
});
|
||||||
mod = mod > min? mod : min;
|
_.forEach(effects.min, function(effect){
|
||||||
break;
|
var min = evaluateEffect(charId, effect);
|
||||||
case "max":
|
mod = mod > min? mod : min;
|
||||||
var max = evaluateEffect(charId, effect);
|
});
|
||||||
mod = mod < max? mod : max;
|
_.forEach(effects.max, function(effect){
|
||||||
break;
|
var max = evaluateEffect(charId, effect);
|
||||||
}
|
mod = mod < max? mod : max;
|
||||||
});
|
});
|
||||||
} finally{
|
return signedString(mod);
|
||||||
//this skill returns or fails, pull it from the array
|
}),
|
||||||
visitedSkills = _.without(visitedSkills, skillName);
|
|
||||||
}
|
|
||||||
return signedString(mod);
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
|
|
||||||
proficiency: function(skillName){
|
proficiency: function(skillName){
|
||||||
var charId = this._id;
|
var charId = this._id;
|
||||||
@@ -418,7 +377,7 @@ Characters.helpers({
|
|||||||
});
|
});
|
||||||
|
|
||||||
//clean up all data related to that character before removing it
|
//clean up all data related to that character before removing it
|
||||||
Characters.before.remove(function (userId, character) {
|
Characters.after.remove(function (userId, character) {
|
||||||
if(Meteor.isServer){
|
if(Meteor.isServer){
|
||||||
Actions .remove({charId: character._id});
|
Actions .remove({charId: character._id});
|
||||||
Attacks .remove({charId: character._id});
|
Attacks .remove({charId: character._id});
|
||||||
|
|||||||
27
rpg-docs/lib/functions/preventLoop.js
Normal file
27
rpg-docs/lib/functions/preventLoop.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
preventLoop = function(inputFunction){
|
||||||
|
if(!_.isFunction(inputFunction)) throw new Meteor.Error("Not a function",
|
||||||
|
"preventLoop can only take a function as an argument");
|
||||||
|
//store a private array of arguments we have been given without returning
|
||||||
|
//if we try to visit the same argument twice before resolving its value
|
||||||
|
//we are in a dependency loop and need to GTFO
|
||||||
|
var visitedArgs = [];
|
||||||
|
return function(argument){
|
||||||
|
var value;
|
||||||
|
//we're still evaluating this attribute, must be in a loop
|
||||||
|
if(_.contains(visitedArgs, argument)) {
|
||||||
|
console.warn("dependency loop detected");
|
||||||
|
return NaN;
|
||||||
|
} else{
|
||||||
|
//push this skill to the list of visited skills
|
||||||
|
//we can't visit it again unless it returns first
|
||||||
|
visitedArgs.push(argument);
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
value = inputFunction.call(this, argument);
|
||||||
|
} finally{
|
||||||
|
//this argument returns or fails, pull it from the array
|
||||||
|
visitedArgs = _.without(visitedArgs, argument);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,8 +1,26 @@
|
|||||||
var getEffect = function(charId, op, value){
|
var getEffect = function(charId, op, value){
|
||||||
|
return getAttributeEffect(charId, "constitution", op, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getAttributeEffect = function(charId, attribute, op, value){
|
||||||
return {
|
return {
|
||||||
charId: charId,
|
charId: charId,
|
||||||
type: "inate",
|
type: "inate",
|
||||||
stat: "constitution",
|
stat: attribute,
|
||||||
|
operation: op,
|
||||||
|
value: value,
|
||||||
|
parent: {
|
||||||
|
id: charId,
|
||||||
|
collection: "Characters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var getSkillEffect = function(charId, op, value){
|
||||||
|
return {
|
||||||
|
charId: charId,
|
||||||
|
type: "inate",
|
||||||
|
stat: "athletics",
|
||||||
operation: op,
|
operation: op,
|
||||||
value: value,
|
value: value,
|
||||||
parent: {
|
parent: {
|
||||||
@@ -15,81 +33,140 @@ var getEffect = function(charId, op, value){
|
|||||||
if (!(typeof MochaWeb === 'undefined')){
|
if (!(typeof MochaWeb === 'undefined')){
|
||||||
MochaWeb.testOnly(function(){
|
MochaWeb.testOnly(function(){
|
||||||
describe("Character", function(){
|
describe("Character", function(){
|
||||||
Effects.remove({});
|
var charId, char, con, ath, strMod;
|
||||||
Characters.remove({});
|
|
||||||
var charId = Characters.insert({owner: "FWeGYyDY5jc4HuTh8"});
|
beforeEach(function(){
|
||||||
var char = Characters.findOne(charId);
|
Effects.remove({});
|
||||||
var con = function(){return char.attributeValue("constitution")};
|
Characters.remove({});
|
||||||
|
charId = Characters.insert({owner: "FWeGYyDY5jc4HuTh8"});
|
||||||
|
char = Characters.findOne(charId);
|
||||||
|
con = function(){return char.attributeValue("constitution")};
|
||||||
|
ath = function(){return char.skillMod("athletics")};
|
||||||
|
strMod = function(){return char.abilityMod("strength")};
|
||||||
|
});
|
||||||
|
|
||||||
describe("effects", function(){
|
describe("effects", function(){
|
||||||
describe("attributeValue", function(){
|
describe("attributeValue", function(){
|
||||||
|
beforeEach(function(){
|
||||||
it("should be set to highest base", function(done){
|
Effects.remove({});
|
||||||
Effects.insert(getEffect(charId, "base", 10), function(err, id){
|
});
|
||||||
if(err) done(err);
|
|
||||||
});
|
it("should be set to highest base", function(){
|
||||||
Effects.insert(getEffect(charId, "base", 6), function(err, id){
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "base", 6));
|
||||||
});
|
|
||||||
chai.assert.equal(10, con());
|
chai.assert.equal(10, con());
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should add", function(done){
|
it("should add", function(){
|
||||||
Effects.insert(getEffect(charId, "add", 2), function(err, id){
|
Effects.insert(getEffect(charId, "add", 2));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
});
|
|
||||||
chai.assert.equal(12, con());
|
chai.assert.equal(12, con());
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should multiply", function(done){
|
it("should multiply after adding", function(){
|
||||||
Effects.insert(getEffect(charId, "mul", 2), function(err, id){
|
Effects.insert(getEffect(charId, "mul", 2));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
});
|
Effects.insert(getEffect(charId, "add", 2));
|
||||||
chai.assert.equal(24, con());
|
chai.assert.equal(24, con());
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be at least highest min", function(done){
|
it("should be at least highest min", function(){
|
||||||
Effects.insert(getEffect(charId, "min", 22), function(err, id){
|
Effects.insert(getEffect(charId, "min", 22));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
});
|
Effects.insert(getEffect(charId, "add", 2));
|
||||||
|
Effects.insert(getEffect(charId, "mul", 2));
|
||||||
chai.assert.equal(con(), 24);
|
chai.assert.equal(con(), 24);
|
||||||
Effects.insert(getEffect(charId, "min", 28), function(err, id){
|
Effects.insert(getEffect(charId, "min", 28));
|
||||||
if(err) done(err);
|
|
||||||
});
|
|
||||||
chai.assert.equal(28, con());
|
chai.assert.equal(28, con());
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be at most lowest max", function(done){
|
it("should be at most lowest max after minning", function(){
|
||||||
Effects.insert(getEffect(charId, "max", 30), function(err, id){
|
Effects.insert(getEffect(charId, "max", 5));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "min", 22));
|
||||||
});
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
chai.assert.equal(28, con());
|
Effects.insert(getEffect(charId, "add", 2));
|
||||||
Effects.insert(getEffect(charId, "max", 5), function(err, id){
|
Effects.insert(getEffect(charId, "mul", 2));
|
||||||
if(err) done(err);
|
chai.assert.equal(5, con());
|
||||||
});
|
Effects.insert(getEffect(charId, "max", 6));
|
||||||
chai.assert.equal(5, con());
|
chai.assert.equal(5, con());
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should respect adjustment", function(done){
|
it("should respect adjustment", function(){
|
||||||
Characters.update(charId, {$set: {"constitution.adjustment": -2}}, function(err, num){
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
if(err) done(err);
|
Effects.insert(getEffect(charId, "add", 2));
|
||||||
})
|
Effects.insert(getEffect(charId, "mul", 2));
|
||||||
|
Effects.insert(getEffect(charId, "min", 28));
|
||||||
|
Effects.insert(getEffect(charId, "max", 5));
|
||||||
|
Characters.update(charId, {$set: {"constitution.adjustment": -2}})
|
||||||
chai.assert.equal(3, con());
|
chai.assert.equal(3, con());
|
||||||
var conBase = char.attributeBase("constitution");
|
var conBase = char.attributeBase("constitution");
|
||||||
chai.assert.equal(5, conBase)
|
chai.assert.equal(5, conBase)
|
||||||
done();
|
});
|
||||||
|
|
||||||
|
it("should be removed when the character is deleted", function(){
|
||||||
|
Effects.insert(getEffect(charId, "base", 10));
|
||||||
|
var count = Effects.find({charId: charId}).count();
|
||||||
|
chai.assert.equal(count, 1);
|
||||||
|
Characters.remove(charId);
|
||||||
|
var count = Effects.find({charId: charId}).count();
|
||||||
|
chai.assert.equal(count, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("skillMod", function(){
|
||||||
|
beforeEach(function(){
|
||||||
|
Effects.remove({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should get its base value from the ability mod", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
chai.assert.equal(3, strMod());
|
||||||
|
chai.assert.equal(3, ath());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add a multiple of proficiency bonus", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
Effects.insert(getAttributeEffect(charId, "proficiencyBonus", "base", 7));
|
||||||
|
chai.assert.equal(7, char.attributeValue("proficiencyBonus"), "the proficiency bonus is calculated correctly");
|
||||||
|
Effects.insert(getSkillEffect(charId, "proficiency", 0.5));
|
||||||
|
Effects.insert(getSkillEffect(charId, "proficiency", 2));
|
||||||
|
chai.assert.equal(17, ath(), "3 strength + (7 x 2) proficiency bonus");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
Effects.insert(getSkillEffect(charId, "add", 2));
|
||||||
|
chai.assert.equal(5, ath());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should multiply", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
Effects.insert(getSkillEffect(charId, "mul", 2));
|
||||||
|
chai.assert.equal(6, ath());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be at least highest min", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
Effects.insert(getSkillEffect(charId, "min", 1));
|
||||||
|
chai.assert.equal(3, ath());
|
||||||
|
Effects.insert(getSkillEffect(charId, "min", 5));
|
||||||
|
chai.assert.equal(5, ath());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be at most lowest max", function(){
|
||||||
|
Effects.insert(getAttributeEffect(charId, "strength", "base", 16));
|
||||||
|
Effects.insert(getSkillEffect(charId, "max", 5));
|
||||||
|
chai.assert.equal(3, ath());
|
||||||
|
Effects.insert(getSkillEffect(charId, "max", 2));
|
||||||
|
chai.assert.equal(2, ath());
|
||||||
|
});
|
||||||
|
|
||||||
it("should be removed when the character is deleted", function(){
|
it("should be removed when the character is deleted", function(){
|
||||||
Characters.remove(charId);
|
Characters.remove(charId);
|
||||||
var count = Effects.find({charId: charId}).count();
|
var count = Effects.find({charId: charId}).count();
|
||||||
chai.assert.equal(count, 0);
|
chai.assert.equal(count, 0);
|
||||||
})
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user