Added very basic testing, got attribute calculations working

This commit is contained in:
Stefan Zermatten
2018-08-24 10:58:59 +02:00
parent f4b1da0c80
commit b21a91b0aa
4 changed files with 176 additions and 37 deletions

View File

@@ -54,3 +54,5 @@ dynamic-import@0.4.0
ddp-rate-limiter@1.0.7 ddp-rate-limiter@1.0.7
rate-limit@1.0.9 rate-limit@1.0.9
iron:router iron:router
meteortesting:mocha
mdg:validated-method

View File

@@ -65,13 +65,19 @@ lai:collection-extensions@0.2.1_1
launch-screen@1.1.1 launch-screen@1.1.1
less@2.7.12 less@2.7.12
livedata@1.0.18 livedata@1.0.18
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0 localstorage@1.2.0
logging@1.1.20 logging@1.1.20
matb33:collection-hooks@0.8.4 matb33:collection-hooks@0.8.4
mdg:validated-method@1.1.0
mdg:validation-error@0.5.1 mdg:validation-error@0.5.1
meteor@1.9.2 meteor@1.9.2
meteor-base@1.4.0 meteor-base@1.4.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4 meteorhacks:subs-manager@1.6.4
meteortesting:browser-tests@1.0.0
meteortesting:mocha@1.0.1
meteortesting:mocha-core@1.0.1
minifier-css@1.3.1 minifier-css@1.3.1
minifier-js@2.3.5 minifier-js@2.3.5
minimongo@1.4.4 minimongo@1.4.4

View File

@@ -1,9 +1,11 @@
// TODO make sure all attributes can only have lowercase, stripped, no spaced names // TODO make sure all attributes can only have lowercase, stripped, no spaced names
// TODO make sure proficiencies are indexed by type // TODO make sure proficiencies are indexed by type
// TODO skills rely on an ability's modifier
import { ValidatedMethod } from 'meteor/mdg:validated-method';
const recomputeCharacter = new ValidatedMethod({ const recomputeCharacter = new ValidatedMethod({
"Characters.methods.recomputeCharacter", // DDP method name name: "Characters.methods.recomputeCharacter", // DDP method name
validate: new SimpleSchema({ validate: new SimpleSchema({
charId: { type: String } charId: { type: String }
@@ -12,6 +14,7 @@ const recomputeCharacter = new ValidatedMethod({
applyOptions: { applyOptions: {
noRetry: true, noRetry: true,
}, },
run({ charId }) { run({ charId }) {
// `this` is the same method invocation object you normally get inside // `this` is the same method invocation object you normally get inside
// Meteor.methods // Meteor.methods
@@ -23,7 +26,7 @@ const recomputeCharacter = new ValidatedMethod({
computeCharacterById(charId); computeCharacterById(charId);
}); },
}); });
@@ -76,6 +79,8 @@ const buildCharacter = function (charId){
atts: {}, atts: {},
skills: {}, skills: {},
dms: {}, dms: {},
classes: {},
level: 0,
}; };
// Fetch the attributes of the character and add them to an object for quick lookup // Fetch the attributes of the character and add them to an object for quick lookup
Attributes.find({charId}).forEach(attribute => { Attributes.find({charId}).forEach(attribute => {
@@ -83,12 +88,12 @@ const buildCharacter = function (charId){
char.atts[attribute.name] = { char.atts[attribute.name] = {
computed: false, computed: false,
busyComputing: false, busyComputing: false,
type: "attribute"; type: "attribute",
result: 0, result: 0,
mod: 0, // The resulting modifier if this is an ability mod: 0, // The resulting modifier if this is an ability
base: 0, base: 0,
add: 0, add: 0,
mul: 0, mul: 1,
min: Number.NEGATIVE_INFINITY, min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY, max: Number.POSITIVE_INFINITY,
effects: [], effects: [],
@@ -102,11 +107,12 @@ const buildCharacter = function (charId){
char.skills[skill.name] = { char.skills[skill.name] = {
computed: false, computed: false,
busyComputing: false, busyComputing: false,
type: "skill"; type: "skill",
ability: skill.ability,
result: 0, // For skills the result is the skillMod result: 0, // For skills the result is the skillMod
proficiency: 0, proficiency: 0,
add: 0, add: 0,
mul: 0, mul: 1,
min: Number.NEGATIVE_INFINITY, min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY, max: Number.POSITIVE_INFINITY,
advantage: 0, advantage: 0,
@@ -126,7 +132,7 @@ const buildCharacter = function (charId){
char.dms[damageMultiplier.name] = { char.dms[damageMultiplier.name] = {
computed: false, computed: false,
busyComputing: false, busyComputing: false,
type: "damageMultiplier"; type: "damageMultiplier",
result: 0, result: 0,
immunityCount: 0, immunityCount: 0,
ressistanceCount: 0, ressistanceCount: 0,
@@ -137,12 +143,11 @@ const buildCharacter = function (charId){
}); });
// Fetch the class levels and store them // Fetch the class levels and store them
char.level = 0; // don't use the word "class" it's reserved
char.classes = {}; Classes.find({charId}).forEach(cls => {
Classes.find({charId}).forEach(class => { if (!char.classes[cls.name]){
if (!char.classes[class.name]){ char.classes[cls.name] = {level: cls.level};
char.classes[class.name] = {level: class.level}; char.level += cls.level;
char.level += class.level;
} }
}); });
@@ -151,14 +156,19 @@ const buildCharacter = function (charId){
charId: charId, charId: charId,
enabled: true, enabled: true,
}).forEach(effect => { }).forEach(effect => {
effect.computed = false; let storedEffect = {
effect.result = 0; computed: false,
result: 0,
operation: effect.operation,
value: effect.value,
calculation: effect.calculation,
}
if (char.atts[effect.stat]) { if (char.atts[effect.stat]) {
char.atts[effect.stat].effects.push(effect); char.atts[effect.stat].effects.push(storedEffect);
} else if (char.skills[effect.stat]) { } else if (char.skills[effect.stat]) {
char.skills[effect.stat].effects.push(effect); char.skills[effect.stat].effects.push(storedEffect);
} else if (char.dms[effect.stat]) { } else if (char.dms[effect.stat]) {
char.dms[effect.stat].effects.push(effect); char.dms[effect.stat].effects.push(storedEffect);
} else { } else {
// ignore effects that don't apply to an actual stat // ignore effects that don't apply to an actual stat
} }
@@ -174,20 +184,24 @@ const buildCharacter = function (charId){
char.skills[proficiency.name].proficiencies.push(effect); char.skills[proficiency.name].proficiencies.push(effect);
} }
}); });
return char;
} }
/* /*
* Compute the character's stats in-place, returns the same char object * Compute the character's stats in-place, returns the same char object
*/ */
const computeCharacter = function (char){ export const computeCharacter = function (char){
// Iterate over each stat in order and compute it // Iterate over each stat in order and compute it
for (stat in char.atts){ for (statName in char.atts){
let stat = char.atts[statName]
computeStat (stat, char); computeStat (stat, char);
} }
for (stat in char.skills){ for (statName in char.skills){
let stat = char.skills[statName]
computeStat (stat, char); computeStat (stat, char);
} }
for (stat in char.dms){ for (statName in char.dms){
let stat = char.dms[statName]
computeStat (stat, char); computeStat (stat, char);
} }
return char; return char;
@@ -211,10 +225,10 @@ const computeStat = function(stat, char){
} }
// Iterate over each effect which applies to the stat // Iterate over each effect which applies to the stat
for (effect in stat.effects){ for (i in stat.effects){
computeEffect(effect, char); computeEffect(stat.effects[i], char);
// apply the effect to the stat // apply the effect to the stat
applyEffect(effect, stat); applyEffect(stat.effects[i], stat);
} }
// Conglomerate all the effects to compute the final stat values // Conglomerate all the effects to compute the final stat values
@@ -229,15 +243,18 @@ const computeStat = function(stat, char){
* Compute a single effect on a character * Compute a single effect on a character
*/ */
const computeEffect = function(effect, char){ const computeEffect = function(effect, char){
if (effect.computed) return;
if (_.isFinite(effect.value)){ if (_.isFinite(effect.value)){
effect.result = effect.value; effect.result = effect.value;
} else if(effect.operation === "conditional"){ } else if(effect.operation === "conditional"){
effect.result = effect.calculation; effect.result = effect.calculation;
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation){ } else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){
effect.result = 1; effect.result = 1;
} else if (_.isString(effect.calculation)){ } else if (_.isString(effect.calculation)){
effect.result = evaluateCalculation(charId, effect.calculation); effect.result = evaluateCalculation(effect.calculation, char);
} }
effect.computed = true;
console.log({effect});
}; };
/* /*
@@ -320,20 +337,37 @@ const combineAttribute = function(stat, char){
if (stat.result < stat.min) stat.result = stat.min; if (stat.result < stat.min) stat.result = stat.min;
if (stat.result > stat.max) stat.result = stat.max; if (stat.result > stat.max) stat.result = stat.max;
// Round everything that isn't the carry multiplier // Round everything that isn't the carry multiplier
if (stat.name !== "carryMultiplier") stat.result = Math.floor(stat.result); if (!stat.decimal) stat.result = Math.floor(stat.result);
stat.mod = Math.floor((stat.result - 10) / 2); if (stat.attributeType === "ability") {
stat.mod = Math.floor((stat.result - 10) / 2);
}
console.log({statResult: stat.result})
} }
const combineSkill = function(stat, char){ const combineSkill = function(stat, char){
for (prof in stat.proficiencies){ for (i in stat.proficiencies){
let prof = stat.proficiencies[i];
if (prof.value > stat.proficiency) stat.proficiency = prof.value; if (prof.value > stat.proficiency) stat.proficiency = prof.value;
} }
if (!char.atts.proficiencyBonus.computed){ let profBonus;
computeStat(char.atts.proficiencyBonus, char); if (char.atts.proificiencyBonus){
if (!char.atts.proficiencyBonus.computed){
computeStat(char.atts.proficiencyBonus, char);
}
profBonus = char.atts.proficiencyBonus.result;
} else {
profBonus = Math.floor(char.level / 4 + 1.75);
} }
const profBonus = char.atts.proficiencyBonus.result; profBonus *= stat.proficiency;
const base = profBonus * stat.proficiency; // Skills are based on some ability Modifier
stat.result = (base + stat.add) * stat.mul; let abilityMod = 0;
if (stat.ability && char.atts[stat.ability]){
if (!char.atts[stat.ability].computed){
computeStat(char.atts[stat.ability], char);
}
abilityMod = char.atts[stat.ability].mod;
}
stat.result = (abilityMod + profBonus + stat.add) * stat.mul;
if (stat.result < stat.min) stat.result = stat.min; if (stat.result < stat.min) stat.result = stat.min;
if (stat.result > stat.max) stat.result = stat.max; if (stat.result > stat.max) stat.result = stat.max;
stat.result = Math.floor(stat.result); stat.result = Math.floor(stat.result);
@@ -341,9 +375,9 @@ const combineSkill = function(stat, char){
const combineDamageMultiplier = function(stat, char){ const combineDamageMultiplier = function(stat, char){
if (stat.immunityCount) return 0; if (stat.immunityCount) return 0;
if (ressistanceCount && !vulnerabilityCount){ if (stat.ressistanceCount && !stat.vulnerabilityCount){
stat.result = 0.5; stat.result = 0.5;
} else if (!ressistanceCount && vulnerabilityCount){ } else if (!stat.ressistanceCount && stat.vulnerabilityCount){
stat.result = 2; stat.result = 2;
} else { } else {
stat.result = 1; stat.result = 1;

View File

@@ -0,0 +1,97 @@
import {computeCharacter} from "./CharacterComputation.js";
import assert from "assert";
const makeEffect = function(operation, value){
let effect = {computed: false, result: 0, operation}
if (_.isFinite(value)){
effect.value = +value;
} else {
effect.calculation = value;
}
return effect;
}
describe('computeCharacter', function () {
it('computes an aritrary character', function () {
let char = {
atts: {
attribute1: {
computed: false,
busyComputing: false,
type: "attribute",
attributeType: "ability",
result: 0,
mod: 0, // The resulting modifier if this is an ability
base: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
effects: [
makeEffect("base", 10),
makeEffect("add", 5),
makeEffect("mul", 2),
],
},
attribute2: {
computed: false,
busyComputing: false,
type: "attribute",
result: 0,
mod: 0, // The resulting modifier if this is an ability
base: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
effects: [
makeEffect("base", "attribute1"),
makeEffect("max", 2),
],
},
},
skills: {
skill1: {
computed: false,
busyComputing: false,
type: "skill",
ability: "attribute1",
result: 0,
proficiency: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
advantage: 0,
disadvantage: 0,
passiveAdd: 0,
fail: 0,
conditional: 0,
effects: [],
proficiencies: [],
},
},
dms: {
dm1: {
computed: false,
busyComputing: false,
type: "damageMultiplier",
result: 0,
immunityCount: 0,
ressistanceCount: 0,
vulnerabilityCount: 0,
effects: [],
}
},
classes: {
Barbarian: {
level: 5,
},
},
level: 5,
};
char = computeCharacter(char);
console.log(char);
assert(false);
});
});