Prevented user-created dependency loops when setting effects as calclations
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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("×").append(value))
|
.append($("<span>").addClass("auditValue").append("×").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;
|
||||||
});
|
};
|
||||||
@@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
18
rpg-docs/lib/functions/placeCaretAtEnd.js
Normal file
18
rpg-docs/lib/functions/placeCaretAtEnd.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user