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
rate-limit@1.0.9
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
less@2.7.12
livedata@1.0.18
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.1.20
matb33:collection-hooks@0.8.4
mdg:validated-method@1.1.0
mdg:validation-error@0.5.1
meteor@1.9.2
meteor-base@1.4.0
meteorhacks:picker@1.0.3
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-js@2.3.5
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 proficiencies are indexed by type
// TODO skills rely on an ability's modifier
import { ValidatedMethod } from 'meteor/mdg:validated-method';
const recomputeCharacter = new ValidatedMethod({
"Characters.methods.recomputeCharacter", // DDP method name
name: "Characters.methods.recomputeCharacter", // DDP method name
validate: new SimpleSchema({
charId: { type: String }
@@ -12,6 +14,7 @@ const recomputeCharacter = new ValidatedMethod({
applyOptions: {
noRetry: true,
},
run({ charId }) {
// `this` is the same method invocation object you normally get inside
// Meteor.methods
@@ -23,7 +26,7 @@ const recomputeCharacter = new ValidatedMethod({
computeCharacterById(charId);
});
},
});
@@ -76,6 +79,8 @@ const buildCharacter = function (charId){
atts: {},
skills: {},
dms: {},
classes: {},
level: 0,
};
// Fetch the attributes of the character and add them to an object for quick lookup
Attributes.find({charId}).forEach(attribute => {
@@ -83,12 +88,12 @@ const buildCharacter = function (charId){
char.atts[attribute.name] = {
computed: false,
busyComputing: false,
type: "attribute";
type: "attribute",
result: 0,
mod: 0, // The resulting modifier if this is an ability
base: 0,
add: 0,
mul: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
effects: [],
@@ -102,11 +107,12 @@ const buildCharacter = function (charId){
char.skills[skill.name] = {
computed: false,
busyComputing: false,
type: "skill";
type: "skill",
ability: skill.ability,
result: 0, // For skills the result is the skillMod
proficiency: 0,
add: 0,
mul: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
advantage: 0,
@@ -126,7 +132,7 @@ const buildCharacter = function (charId){
char.dms[damageMultiplier.name] = {
computed: false,
busyComputing: false,
type: "damageMultiplier";
type: "damageMultiplier",
result: 0,
immunityCount: 0,
ressistanceCount: 0,
@@ -137,12 +143,11 @@ const buildCharacter = function (charId){
});
// Fetch the class levels and store them
char.level = 0;
char.classes = {};
Classes.find({charId}).forEach(class => {
if (!char.classes[class.name]){
char.classes[class.name] = {level: class.level};
char.level += class.level;
// don't use the word "class" it's reserved
Classes.find({charId}).forEach(cls => {
if (!char.classes[cls.name]){
char.classes[cls.name] = {level: cls.level};
char.level += cls.level;
}
});
@@ -151,14 +156,19 @@ const buildCharacter = function (charId){
charId: charId,
enabled: true,
}).forEach(effect => {
effect.computed = false;
effect.result = 0;
let storedEffect = {
computed: false,
result: 0,
operation: effect.operation,
value: effect.value,
calculation: effect.calculation,
}
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]) {
char.skills[effect.stat].effects.push(effect);
char.skills[effect.stat].effects.push(storedEffect);
} else if (char.dms[effect.stat]) {
char.dms[effect.stat].effects.push(effect);
char.dms[effect.stat].effects.push(storedEffect);
} else {
// 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);
}
});
return char;
}
/*
* 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
for (stat in char.atts){
for (statName in char.atts){
let stat = char.atts[statName]
computeStat (stat, char);
}
for (stat in char.skills){
for (statName in char.skills){
let stat = char.skills[statName]
computeStat (stat, char);
}
for (stat in char.dms){
for (statName in char.dms){
let stat = char.dms[statName]
computeStat (stat, char);
}
return char;
@@ -211,10 +225,10 @@ const computeStat = function(stat, char){
}
// Iterate over each effect which applies to the stat
for (effect in stat.effects){
computeEffect(effect, char);
for (i in stat.effects){
computeEffect(stat.effects[i], char);
// apply the effect to the stat
applyEffect(effect, stat);
applyEffect(stat.effects[i], stat);
}
// 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
*/
const computeEffect = function(effect, char){
if (effect.computed) return;
if (_.isFinite(effect.value)){
effect.result = effect.value;
} else if(effect.operation === "conditional"){
effect.result = effect.calculation;
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation){
} else if(_.contains(["advantage", "disadvantage", "fail"], effect.operation)){
effect.result = 1;
} 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.max) stat.result = stat.max;
// Round everything that isn't the carry multiplier
if (stat.name !== "carryMultiplier") stat.result = Math.floor(stat.result);
stat.mod = Math.floor((stat.result - 10) / 2);
if (!stat.decimal) stat.result = Math.floor(stat.result);
if (stat.attributeType === "ability") {
stat.mod = Math.floor((stat.result - 10) / 2);
}
console.log({statResult: stat.result})
}
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 (!char.atts.proficiencyBonus.computed){
computeStat(char.atts.proficiencyBonus, char);
let profBonus;
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;
const base = profBonus * stat.proficiency;
stat.result = (base + stat.add) * stat.mul;
profBonus *= stat.proficiency;
// Skills are based on some ability Modifier
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.max) stat.result = stat.max;
stat.result = Math.floor(stat.result);
@@ -341,9 +375,9 @@ const combineSkill = function(stat, char){
const combineDamageMultiplier = function(stat, char){
if (stat.immunityCount) return 0;
if (ressistanceCount && !vulnerabilityCount){
if (stat.ressistanceCount && !stat.vulnerabilityCount){
stat.result = 0.5;
} else if (!ressistanceCount && vulnerabilityCount){
} else if (!stat.ressistanceCount && stat.vulnerabilityCount){
stat.result = 2;
} else {
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);
});
});