Added very basic testing, got attribute calculations working
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
97
app/Model/Character/CharacterComputation.test.js
Normal file
97
app/Model/Character/CharacterComputation.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user