Prevented user-created dependency loops when setting effects as calclations

This commit is contained in:
Thaum
2014-11-20 12:55:42 +00:00
parent ca7a625534
commit 252d0f989b
7 changed files with 146 additions and 111 deletions

View File

@@ -3,17 +3,17 @@ Characters = new Meteor.Collection("characters");
Schemas.Character = new SimpleSchema({ Schemas.Character = new SimpleSchema({
//strings //strings
name: { type: String, defaultValue: "", optional: true }, name: { type: String, defaultValue: "", trim: false},
alignment: { type: String, defaultValue: "", optional: true }, alignment: { type: String, defaultValue: "", trim: false},
gender: { type: String, defaultValue: "", optional: true }, gender: { type: String, defaultValue: "", trim: false},
race: { type: String, defaultValue: "", optional: true }, race: { type: String, defaultValue: "", trim: false},
description:{ type: String, defaultValue: "", optional: true }, description:{ type: String, defaultValue: "", trim: false},
personality:{ type: String, defaultValue: "", optional: true }, personality:{ type: String, defaultValue: "", trim: false},
ideals: { type: String, defaultValue: "", optional: true }, ideals: { type: String, defaultValue: "", trim: false},
bonds: { type: String, defaultValue: "", optional: true }, bonds: { type: String, defaultValue: "", trim: false},
flaws: { type: String, defaultValue: "", optional: true }, flaws: { type: String, defaultValue: "", trim: false},
backstory: { type: String, defaultValue: "", optional: true }, backstory: { type: String, defaultValue: "", trim: false},
notes: { type: String, defaultValue: "", optional: true }, notes: { type: String, defaultValue: "", trim: false},
//attributes //attributes
//ability scores //ability scores
@@ -269,7 +269,7 @@ Characters.helpers({
var char = Characters.findOne(this._id, {fields: fieldSelector}); var char = Characters.findOne(this._id, {fields: fieldSelector});
var field = char[fieldName]; var field = char[fieldName];
if(field === undefined){ if(field === undefined){
throw "no such field "+fieldName+" exists"; console.log("no field ", fieldName, " exists for character ", char)
} }
return field; return field;
}, },
@@ -279,52 +279,123 @@ Characters.helpers({
console.log("Character's schema does not contain a field called: " + fieldName); console.log("Character's schema does not contain a field called: " + fieldName);
return; return;
} }
var field = this.getField(fieldName);
//duck typing to get the right value function //duck typing to get the right value function
//.proficiency implies skill //.proficiency implies skill
if (Schemas.Character.schema(fieldName + ".proficiency")){ if (Schemas.Character.schema(fieldName + ".proficiency")){
return this.skillMod(field); return this.skillMod(fieldName);
} }
//base without proficiency implies attribute //base without proficiency implies attribute
if (Schemas.Character.schema(fieldName + ".base")){ if (Schemas.Character.schema(fieldName + ".base")){
return this.attributeValue(field); return this.attributeValue(fieldName);
} }
//fall back to just returning the field itself //fall back to just returning the field itself
return field; return this.getField(fieldName);
}, },
attributeValue: function(attribute){ attributeValue: (function(){
if (attribute === undefined) return; //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
if (_.isString(attribute)){ //we are in a dependency loop and need to GTFO
attribute = this.getField(attribute); 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);
var charId = this._id;
var attribute = this.getField(attributeName);
//base value
var value = attribute.base;
//add all values in add array
_.each(attribute.add, function(effect){
value += evaluateEffect(charId, effect);
});
//multiply all values in mul array
_.each(attribute.mul, function(effect){
value *= evaluateEffect(charId, effect);
});
//largest min
_.each(attribute.min, function(effect){
var min = evaluateEffect(charId, effect);
value = value > min? value : min;
});
//smallest max
_.each(attribute.max, function(effect){
var max = evaluateEffect(charId, effect);
value = value < max? value : max;
});
//done traversing the tree, this attribute returns, pull it from the array
visitedAttributes = _.without(visitedAttributes, attributeName);
return value;
} }
//base value })(),
var value = attribute.base;
//add all values in add array
_.each(attribute.add, function(effect){
value += evaluateEffect(charId, effect);
});
//multiply all values in mul array skillMod: (function(){
_.each(attribute.mul, function(effect){ //store a private array of skills we've visited without returning
value *= evaluateEffect(charId, effect); //if we try to visit the same skill twice before resolving its value
}); //we are in a dependency loop and need to GTFO
var visitedSkills = [];
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);
//largest min var charId = this._id;
_.each(attribute.min, function(effect){ skill = this.getField(skillName);
var min = evaluateEffect(charId, effect); //get the final value of the ability score
value = value > min? value : min; var ability = this.attributeValue(skill.ability);
});
//smallest max //base modifier
_.each(attribute.max, function(effect){ var mod = +getMod(ability)
var max = evaluateEffect(charId, effect);
value = value < max? value : max;
});
return value; //multiply proficiency bonus by largest value in proficiency array
}, var prof = this.proficiency(skill);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
//add all values in add array
_.each(skill.add, function(effect){
mod += evaluateEffect(charId, effect);
});
//multiply all values in mul array
_.each(skill.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
//largest min
_.each(skill.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min? mod : min;
});
//smallest max
_.each(skill.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max? mod : max;
});
//done traversing the tree, this skill returns, pull it from the array
visitedSkills = _.without(visitedSkills, skillName);
return signedString(mod);
}
})(),
proficiency: function(skill){ proficiency: function(skill){
if (_.isString(skill)){ if (_.isString(skill)){
@@ -342,52 +413,6 @@ Characters.helpers({
return prof; return prof;
}, },
skillMod: function(skill){
if(skill === undefined){
console.log("Cannot get skillMod of undefined");
return;
}
var charId = this._id;
if (_.isString(skill)){
skill = this.getField(skill);
}
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
//base modifier
var mod = +getMod(ability)
//multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skill);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
//add all values in add array
_.each(skill.add, function(effect){
mod += evaluateEffect(charId, effect);
});
//multiply all values in mul array
_.each(skill.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
//largest min
_.each(skill.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min? mod : min;
});
//smallest max
_.each(skill.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max? mod : max;
});
return signedString(mod);
},
passiveSkill: function(skill){ passiveSkill: function(skill){
if (_.isString(skill)){ if (_.isString(skill)){
skill = this.getField(skill); skill = this.getField(skill);

View File

@@ -1,10 +1,11 @@
Template.registerHelper("effectList", function(obj, character){ /*Template.registerHelper(function("effectList", */var disabled = function(charId, fieldName){
var obj = Characters.findOne(charId, {fields: {_id: 1}}).getField(fieldName);
var result = $("<div>"); var result = $("<div>");
if(_.has(obj, "conditional") && obj.conditional.length > 0){ if(_.has(obj, "conditional") && obj.conditional.length > 0){
_.each(obj.conditional, function(cond){ _.each(obj.conditional, function(cond){
var c = $("<div>") var c = $("<div>")
.append("* ") .append("* ")
.append(evaluateString(character, cond.name)); .append(evaluateString(charId, cond.name));
result.append(c); result.append(c);
}); });
} }
@@ -19,7 +20,7 @@ Template.registerHelper("effectList", function(obj, character){
if(_.has(obj, "proficiency") && obj.proficiency.length > 0){ if(_.has(obj, "proficiency") && obj.proficiency.length > 0){
var highestProf = {}; var highestProf = {};
_.each(obj.proficiency, function(prof, i){ _.each(obj.proficiency, function(prof, i){
var value = evaluateEffect(character, prof) var value = evaluateEffect(charId, prof)
if(i === 0 || value > highestProf.value){ if(i === 0 || value > highestProf.value){
highestProf.value = value; highestProf.value = value;
highestProf.name = prof.name; highestProf.name = prof.name;
@@ -34,7 +35,7 @@ Template.registerHelper("effectList", function(obj, character){
if(_.has(obj, "add") && obj.add.length > 0){ if(_.has(obj, "add") && obj.add.length > 0){
_.each(obj.add, function(a){ _.each(obj.add, function(a){
var value = signedString(evaluateEffect(character, a)); var value = signedString(evaluateEffect(charId, a));
var c = $("<div>") var c = $("<div>")
.append($("<span>").addClass("auditValue").append(value)) .append($("<span>").addClass("auditValue").append(value))
.append(a.name); .append(a.name);
@@ -44,7 +45,7 @@ Template.registerHelper("effectList", function(obj, character){
if(_.has(obj, "mul") && obj.mul.length > 0){ if(_.has(obj, "mul") && obj.mul.length > 0){
_.each(obj.mul, function(a){ _.each(obj.mul, function(a){
var value = signedString(evaluateEffect(character, a)); var value = signedString(evaluateEffect(charId, a));
var c = $("<div>") var c = $("<div>")
.append($("<span>").addClass("auditValue").append("&times;").append(value)) .append($("<span>").addClass("auditValue").append("&times;").append(value))
.append(a.name); .append(a.name);
@@ -55,7 +56,7 @@ Template.registerHelper("effectList", function(obj, character){
if(_.has(obj, "min") && obj.min.length > 0){ if(_.has(obj, "min") && obj.min.length > 0){
var highestMin = {}; var highestMin = {};
_.each(obj.min, function(m, i){ _.each(obj.min, function(m, i){
var value = evaluateEffect(character, m) var value = evaluateEffect(charId, m)
if(i === 0 || value > highestMin.value){ if(i === 0 || value > highestMin.value){
highestMin.value = value; highestMin.value = value;
highestMin.name = m.name; highestMin.name = m.name;
@@ -71,7 +72,7 @@ Template.registerHelper("effectList", function(obj, character){
if(_.has(obj, "max") && obj.max.length > 0){ if(_.has(obj, "max") && obj.max.length > 0){
var lowestMax = {}; var lowestMax = {};
_.each(obj.max, function(m, i){ _.each(obj.max, function(m, i){
var value = evaluateEffect(character, m) var value = evaluateEffect(charId, m)
if(i === 0 || value < lowestMax.value){ if(i === 0 || value < lowestMax.value){
lowestMax.value = value; lowestMax.value = value;
lowestMax.name = m.name; lowestMax.name = m.name;
@@ -120,4 +121,4 @@ Template.registerHelper("effectList", function(obj, character){
var string = result.html() var string = result.html()
if (_.isString(string)) return Spacebars.SafeString(string); if (_.isString(string)) return Spacebars.SafeString(string);
return string; return string;
}); };

View File

@@ -1,8 +0,0 @@
_.each(Template, function (template, name) {
if(template){
var counter = 0;
template.rendered = template.rendered || function () {
console.log(name, "render count: ", ++counter);
};
}
});

View File

@@ -2,7 +2,7 @@
<h2>Saving Throws</h2> <h2>Saving Throws</h2>
<table class="skillTable"> <table class="skillTable">
{{#each saveList}} {{#each saveList}}
<tr title={{effectList skill ..}}> <tr>
<td><div class="profIcon" style="background-image: url(/png/profIcons/{{profIcon skill}})"></div></td> <td><div class="profIcon" style="background-image: url(/png/profIcons/{{profIcon skill}})"></div></td>
{{#if failSkill}} {{#if failSkill}}
<td class="fail">fail</td> <td class="fail">fail</td>
@@ -16,7 +16,7 @@
<h2>Skills</h2> <h2>Skills</h2>
<table class="skillTable"> <table class="skillTable">
{{#each skillList}} {{#each skillList}}
<tr title={{effectList skill ..}}> <tr>
<td><div class="profIcon" style="background-image: url(/png/profIcons/{{profIcon skill}})"></div></td> <td><div class="profIcon" style="background-image: url(/png/profIcons/{{profIcon skill}})"></div></td>
{{#if failSkill}} {{#if failSkill}}
<td class="fail">fail</td> <td class="fail">fail</td>

View File

@@ -34,14 +34,13 @@ Template.textField.events({
//TODO sanitise the html //TODO sanitise the html
var setter = {}; var setter = {};
setter[this.field] = text; setter[this.field] = text;
Characters.update(this.character._id, {$set: setter}, function(error, result) { Characters.update(this.character._id, {$set: setter}, {removeEmptyStrings: false});
if(error) console.log(error);
});
}, },
"click #textOutput": function(){ "click #textOutput": function(){
Template.instance().editing.set(true); Template.instance().editing.set(true);
Tracker.afterFlush(function(){ Tracker.afterFlush(function(){
$("#textInput").focus(); $("#textInput").focus();
placeCaretAtEnd($("#textInput"));
}); });
} }
}) })

View File

@@ -24,7 +24,7 @@ evaluate = function(charId, string){
} catch(e){ } catch(e){
console.log(e) console.log(e)
return string; return string;
} }
} }
//takes a string with {calculations} and returns it with the results //takes a string with {calculations} and returns it with the results

View File

@@ -0,0 +1,18 @@
placeCaretAtEnd = function(el) {
el = el.get(0); //jquery element -> DOM element
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}