Changed rpg-docs folder to app

This commit is contained in:
Stefan Zermatten
2018-05-21 14:21:07 +02:00
parent 45e9f491ff
commit 52baf297ca
419 changed files with 12 additions and 13 deletions

View File

@@ -0,0 +1,258 @@
characterExport = function(charId){
var {
character, classes, effects, proficiencies,
} = getCharacterForComputation(charId);
var char = character;
computedCharacter = computeCharacter({
character, classes, effects, proficiencies,
});
if (!char) {
return {
error: charId + " character not found"
};
}
if (char.settings.viewPermission !== "public" && Meteor.isServer){
return {
error: charId + " character is not viewable to anyone with link"
};
}
var baseValue = function(attributeName){
var attribute = computedCharacter[attributeName];
return attribute && attribute.value;
};
var attributeValue = function(attributeName){
var base = baseValue(attributeName);
var adjustment = char[attributeName] && char[attributeName].adjustment;
return base + adjustment;
};
var abilityMod = function(attributeName){
return signedString(getMod(attributeValue(attributeName)));
};
var skillMod = function(skillName){
return signedString(baseValue(skillName));
};
var proficiency = function(skillName){
var skill = computedCharacter[skillName];
return skill && skill.proficiency;
};
var passiveSkill = function(skillName){
var attribute = computedCharacter[skillName];
if (!attribute) return;
return 10 + baseValue(skillName) + attribute.passiveAdd;
};
var experience = function(){
var xp = 0;
Experiences.find(
{charId: charId},
{fields: {value: 1}}
).forEach(function(e){
xp += e.value;
});
return xp;
};
var getClasses = function(){
return _.map(classes, c => `${c.name} ${c.level}`).join(", ");
};
var getHitDiceString = function(){
var d6 = baseValue("d6HitDice");
var d8 = baseValue("d8HitDice");
var d10 = baseValue("d10HitDice");
var d12 = baseValue("d12HitDice");
var con = abilityMod("constitution");
var string = "" +
(d6 ? `${d6}d6 + ` : "") +
(d8 ? `${d8}d8 + ` : "") +
(d10 ? `${d10}d10 + ` : "") +
(d12 ? `${d12}d12 + ` : "") +
con;
return string;
}
var getSkills = function(charId){
var allSkills = [
{name: "acrobatics", attribute: "dexterity"},
{name: "animalHandling", attribute: "wisdom"},
{name: "arcana", attribute: "intelligence"},
{name: "athletics", attribute: "strength"},
{name: "deception", attribute: "charisma"},
{name: "history", attribute: "intelligence"},
{name: "insight", attribute: "wisdom"},
{name: "intimidation", attribute: "charisma"},
{name: "investigation", attribute: "intelligence"},
{name: "medicine", attribute: "wisdom"},
{name: "nature", attribute: "intelligence"},
{name: "perception", attribute: "wisdom"},
{name: "performance", attribute: "charisma"},
{name: "persuasion", attribute: "charisma"},
{name: "religion", attribute: "intelligence"},
{name: "sleightOfHand", attribute: "dexterity"},
{name: "stealth", attribute: "dexterity"},
{name: "survival", attribute: "wisdom"},
];
var skills = {};
_.each(allSkills, skill => {
var value = skillMod(skill.name);
var prof = proficiency(skill.name);
var name = skill.name.charAt(0).toUpperCase() + skill.name.slice(1);
skills[name] = value;
skills[name + "Proficiency"] = prof;
});
return skills;
};
var character = {
"Id": char._id,
"Name": char.name,
"Source": "DiceCloud",
"Alignment": char.alignment || "",
"Gender": char.gender || "",
"Race": char.race || "",
"Level": _.reduce(classes, (memo, cls) => memo + cls.level, 0),
"Experience": experience(),
"Class": getClasses(charId),
"HPBase": baseValue("hitPoints"),
"HPValue": attributeValue("hitPoints"),
"HitDice": getHitDiceString(charId) || "",
"AC": attributeValue("armor"),
"Initiative": skillMod("initiative"),
"Speed": attributeValue("speed"),
"ProficiencyBonus": attributeValue("proficiencyBonus"),
"passivePerception": passiveSkill("perception"),
"Languages": getLanguages(charId),
"Description": char.description || "",
"Backstory": char.backstory || "",
"Personality": char.personality || "" ,
"Bonds": char.bonds || "",
"Ideals": char.ideals || "",
"Flaws": char.flaws || "",
"PictureURL": char.picture || "",
"Strength": attributeValue("strength"),
"Dexterity": attributeValue("dexterity"),
"Constitution": attributeValue("constitution"),
"intelligence": attributeValue("intelligence"),
"Wisdom": attributeValue("wisdom"),
"Charisma": attributeValue("charisma"),
"StrengthMod": abilityMod("strength"),
"DexterityMod": abilityMod("dexterity"),
"ConstitutionMod": abilityMod("constitution"),
"intelligenceMod": abilityMod("intelligence"),
"WisdomMod": abilityMod("wisdom"),
"CharismaMod": abilityMod("charisma"),
//"DamageVulnerabilities": damageMods.vulnerabilities,
//"DamageResistances": damageMods.resistances,
//"DamageImmunities": damageMods.immunities,
"StrengthSave": skillMod("strengthSave"),
"StrengthSaveProficiency": proficiency("strengthSave"),
"DexteritySave": skillMod("dexteritySave"),
"DexteritySaveProficiency": proficiency("dexteritySave"),
"ConstitutionSave": skillMod("constitutionSave"),
"ConstitutionSaveProficiency": proficiency("constitutionSave"),
"intelligenceSave": skillMod("intelligenceSave"),
"intelligenceSaveProficiency": proficiency("intelligenceSave"),
"WisdomSave": skillMod("wisdomSave"),
"WisdomSaveProficiency": proficiency("wisdomSave"),
"CharismaSave": skillMod("charismaSave"),
"CharismaSaveProficiency": proficiency("charismaSave"),
"Level1SpellSlots": attributeValue("level1SpellSlots"),
"Level2SpellSlots": attributeValue("level2SpellSlots"),
"Level3SpellSlots": attributeValue("level3SpellSlots"),
"Level4SpellSlots": attributeValue("level4SpellSlots"),
"Level5SpellSlots": attributeValue("level5SpellSlots"),
"Level6SpellSlots": attributeValue("level6SpellSlots"),
"Level7SpellSlots": attributeValue("level7SpellSlots"),
"Level8SpellSlots": attributeValue("level8SpellSlots"),
"Level9SpellSlots": attributeValue("level9SpellSlots"),
"Ki": attributeValue("ki"),
"Rages": attributeValue("rages"),
"RageDamage": attributeValue("rageDamage"),
"SorceryPoints": attributeValue("sorceryPoints"),
"DeathSavePasses": char.deathSave.pass,
"DeathSaveFails": char.deathSave.fail,
"DeathSaveStable": char.deathSave.stable,
};
_.extend(character, getSkills(charId));
_.extend(character, getAttacks(charId));
return character;
}
var getArmorString = function(charId){
var bases = Effects.find({
charId: charId,
stat: "armor",
operation: "base",
enabled: true,
}).map(e => ({
ame: e.name,
value: evaluateEffect(charId, e),
}));
var base = bases.length && _.max(bases, b => b.value).name || "";
var effects = Effects.find({
charId: charId,
stat: "armor",
operation: {$ne: "base"},
enabled: true,
}).map(e => e.name);
var strings = base ? [base] : [];
strings = strings.concat(effects);
return strings.join(", ");
}
/*
var getDamageMods = function(charId){
// jscs:disable maximumLineLength
var multipliers = [
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
];
// jscs:enable maximumLineLength
multipliers = _.groupBy(multipliers, "value");
var names = o => o.name;
return {
"immunities": _.map(multipliers["0"], names).join(", "),
"resistances": _.map(multipliers["0.5"], names).join(", "),
"vulnerabilities": _.map(multipliers["2"], names).join(", "),
};
}
*/
var getLanguages = function(charId){
return Proficiencies.find({
charId,
enabled: true,
type: "language",
}).map(l => l.name).join(", ");
};
var getAttacks = function(charId){
var attacks = {};
var i = 1;
Attacks.find(
{charId, enabled: true},
{sort: {color: 1, name: 1}}
).forEach(a => {
attacks[`Attack${i++}`] = a.name +
` +${evaluate(charId, a.attackBonus)} to hit, ` +
`${evaluateString(charId, a.damage)} ${a.damageType} damage, ` +
`${a.details}`;
});
return attacks;
};
var signedString = function(number) {
return number >= 0 ? "+" + number : "" + number;
};

View File

@@ -0,0 +1,7 @@
getMod = function(score){
return Math.floor((score - 10) / 2);
};
signedString = function(number){
return number >= 0 ? "+" + number : "" + number;
};

View File

@@ -0,0 +1,224 @@
getCharacterForComputation = function(charId){
const character = Characters.findOne(charId);
const classes = Classes.find({charId}).fetch();
const effects = Effects.find({charId, enabled: true}).fetch();
const proficiencies = Proficiencies.find({
charId,
enabled: true,
type: {$in: ["skill", "save"]},
}).fetch();
return {character, classes, effects, proficiencies};
}
computeCharacter = function({character, classes, effects, proficiencies}){
var charId = character._id;
let computedClasses = computeCharacterClasses(charId, classes);
let changed = false;
computedCharacter = {};
let i;
for (i = 0; i < 15; i++){
[computedCharacter, changed] = compute({
classes: computedClasses,
oldChar: computedCharacter,
charId,
character,
effects,
proficiencies,
});
if (!changed) break;
}
return computedCharacter;
};
var ensureCharacterExists = (character) => {
if (!character) {
throw new Meteor.Error("Character doesn't exist",
"You can't recompute a character that doesn't exist");
}
};
var ensureWritePermissions = (character, userId) => {
if (
userId &&
userId !== character.owner &&
!_.contains(character.writers, userId)
){
throw new Meteor.Error("Character write denied",
"You don't have permission to recompute this character");
}
};
var computeCharacterClasses = function(charId, classes){
let computedClasses = {};
_.each(classes, (cls) => {
if (computedClasses[cls.name]){
computedClasses[cls.name].level += cls.level;
} else {
computedClasses[cls.name] = cls;
}
});
return computedClasses;
}
var compute = function({
charId, oldChar, character, classes, effects, proficiencies,
}){
let newChar = {};
_.each(effects, (effect, index) => {
if (!effect.stat || effect.operation === "conditional") return;
if (!newChar[effect.stat]) newChar[effect.stat] = defaultStat();
let value = effect.calculation ?
computeEffect(effect.calculation, oldChar, classes) :
effect.value || 0;
let stat = newChar[effect.stat];
if (!_.isNumber(value)) return;
switch (effect.operation) {
case "base":
if (value > stat.base) stat.base = value;
break;
case "proficiency":
if (value > stat.proficiency) stat.proficiency = value;
break;
case "add":
stat.add += value;
break;
case "mul":
stat.mul *= value;
break;
case "min":
if (value > stat.min) stat.min = value;
break;
case "max":
if (value < stat.max) stat.max = value;
break;
case "advantage":
stat.advantage++;
break;
case "disadvantage":
stat.disadvantage++;
break;
case "passiveAdd":
stat.passiveAdd += value;
break;
case "fail":
stat.fail = true;
break;
}
});
_.each(proficiencies, proficiency => {
if (!proficiency.name) return;
if (!newChar[proficiency.name]) newChar[proficiency.name] = defaultStat();
let stat = newChar[proficiency.name];
let value = proficiency.value;
if (value > stat.proficiency) stat.proficiency = value;
});
let changed = false;
_.each(ATTRIBUTES, function(statName) {
if (!newChar[statName]) newChar[statName] = defaultStat();
let stat = newChar[statName];
stat.value = (stat.base + stat.add) * stat.mul;
if (stat.value < stat.min) stat.value = stat.min;
if (stat.value > stat.max) stat.value = stat.max;
if (!_.isEqual(stat.value, oldChar[statName] && oldChar[statName].value)){
changed = true;
}
});
_.each(ALL_SKILLS, function(statName) {
if (!newChar[statName]) newChar[statName] = defaultStat();
let stat = newChar[statName];
stat.value = characterAbilityMod(
oldChar, character[statName] && character[statName].ability
);
stat.value += stat.base + stat.add;
stat.value += stat.proficiency *
characterFieldValue(oldChar, "proficiencyBonus");
stat.value *= stat.mul;
if (stat.value < stat.min) stat.value = stat.min;
if (stat.value > stat.max) stat.value = stat.max;
if (!_.isEqual(stat.value, oldChar[statName] && oldChar[statName].value)){
changed = true;
}
});
return [newChar, changed];
};
var defaultStat = function(){
return {
base: 0,
proficiency: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
advantage: 0,
disadvantage: 0,
passiveAdd: 0,
fail: false,
}
}
var computeEffect = function(string, character, classes){
if (!string) return string;
string = string.replace(/\b[a-z][\w]+/gi, function(sub){
//fields
if (character[sub]){
return characterFieldValue(character, sub);
}
//ability modifiers
if (_.contains(ABILITY_MODS, sub)){
var slice = sub.slice(0, -3);
return getMod(
character[slice] ? characterFieldValue(character, slice) : 0
);
}
//class levels
if (/\w+levels?\b/gi.test(sub)){
//strip out "level"
var className = sub.replace(/levels?\b/gi, "");
return characterClassLevel(classes, className)
}
//character level
if (sub.toUpperCase() === "LEVEL"){
return characterTotalLevel(classes);
}
// exclude math functions
if (math[sub]){
return sub;
}
return 0;
});
try {
var result = math.eval(string);
return result;
} catch (e){
return string;
}
};
var characterFieldValue = function(character, field){
if (_.isNumber(character[field] && character[field].value)){
return character[field].value;
} else {
return field;
}
};
var characterClassLevel = function(classes, className){
if (_.isNumber(classes[className] && classes[className].level)){
return classes[className].level;
} else {
return className;
}
};
var characterTotalLevel = function(classes){
return _.reduce(classes, (memo, cls) => memo + cls.level, 0);
};
var characterAbilityMod = function(character, abilityName){
if (_.isNumber(character[abilityName] && character[abilityName].value)){
return getMod(character[abilityName].value);
} else {
return 0;
}
};

View File

@@ -0,0 +1,104 @@
// if we want to add more functions, consider pulling out into its own file
(function() {
math.import({
"if": function(pred, a, b) {
return (!!(pred)) ? a : b;
}
});
})();
//evaluates a calculation string
evaluate = function(charId, string, opts){
var spellListId = opts && opts.spellListId;
if (!string) return string;
string = string.replace(/\b[a-z,1-9]+\b/gi, function(sub){
//fields
if (Schemas.Character.schema(sub)){
return Characters.calculate.fieldValue(charId, sub);
}
//ability modifiers
var abilityMods = [
"strengthMod",
"dexterityMod",
"constitutionMod",
"intelligenceMod",
"wisdomMod",
"charismaMod",
];
if (_.contains(abilityMods, sub)){
var slice = sub.slice(0, -3);
try {
return Characters.calculate.abilityMod(charId, slice);
} catch (e){
return sub;
}
}
//class levels
if (/\w+levels?\b/gi.test(sub)){
//strip out "level"
var className = sub.replace(/levels?\b/gi, "");
var cls = Classes.findOne({charId: charId, name: className});
return cls && cls.level || sub;
}
//character level
if (sub.toUpperCase() === "LEVEL"){
return Characters.calculate.level(charId);
}
if (spellListId && sub.toUpperCase() === "DC") {
var list = SpellLists.findOne(spellListId);
if (list && list.saveDC){
return evaluate(charId, list.saveDC);
}
}
if (spellListId && sub.toUpperCase() === "ATTACKBONUS") {
var list = SpellLists.findOne(spellListId);
if (list && list.attackBonus){
return evaluate(charId, list.attackBonus);
}
}
return sub;
});
try {
var result = math.eval(string);
return result;
} catch (e){
return string;
}
};
//takes a string with {calculations} and returns it with the results
//of the calculations returned in place
evaluateString = function(charId, string){
//define brackets as curly brackets around anything that isn't a curly bracket
if (!string) return string;
var brackets = /\{[^\{\}]*\}/g;
var result = string.replace(brackets, function(exp){
exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets
return evaluate(charId, exp);
});
return result;
};
evaluateSpellString = function (charId, spellListId, string) {
//define brackets as curly brackets around anything that isn't a curly bracket
if (!string) return string;
var brackets = /\{[^\{\}]*\}/g;
var result = string.replace(brackets, function(exp){
exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets
return evaluate(charId, exp, {spellListId});
});
return result;
}
//returns the value of the effect if it exists,
//otherwise returns the result of the calculation if it exists,
//otherwise returns 0
evaluateEffect = function(charId, effect){
if (_.isFinite(effect.value)){
return effect.value;
} else if (_.isString(effect.calculation)){
return +evaluate(charId, effect.calculation);
} else {
return 0;
}
};

View File

@@ -0,0 +1,219 @@
var childSchema = new SimpleSchema({
parent: {type: Object},
"parent.collection": {type: String},
"parent.id": {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
"parent.group": {type: String, optional: true},
"removedWith": {
optional: true,
type: String,
regEx: SimpleSchema.RegEx.Id,
},
});
var joinWithDefaultKeys = function(keys){
var defaultKeys = [
"charId",
];
return _.union(keys, defaultKeys);
};
var limitModifierToKeys = function(modifier, keys){
if (!modifier) return;
modifier = _.pick(modifier, ["$set", "$unset"]);
if (modifier.$set) modifier.$set = _.pick(modifier.$set, keys);
if (modifier.$unset) modifier.$unset = _.pick(modifier.$unset, keys);
if (_.isEmpty(modifier.$set)) delete modifier.$set;
if (_.isEmpty(modifier.$unset)) delete modifier.$unset;
return modifier;
};
var getParent = function(doc){
if (!doc || !doc.parent) return;
var parentCol = Meteor.isClient ?
window[doc.parent.collection] : global[doc.parent.collection];
if (parentCol)
return parentCol.findOne(doc.parent.id, {removed: true});
};
var inheritParentProperties = function(doc, collection){
var parent = getParent(doc);
if (!parent) throw new Meteor.Error(
"Parenting Error",
"Document's parent does not exist"
);
var handMeDowns = _.pick(parent, collection.inheritedKeys);
if (
_.contains(collection.inheritedKeys, "charId") &&
doc.parent.collection === "Characters"
){
handMeDowns.charId = doc.parent.id;
}
if (_.isEmpty(handMeDowns)) return;
collection.update(doc._id, {$set: handMeDowns});
};
var childCollections = [];
makeChild = function(collection, inheritedKeys){
inheritedKeys = inheritedKeys || [];
if (inheritedKeys) {
collection.inheritedKeys = joinWithDefaultKeys(inheritedKeys);
}
collection.helpers({
//returns the parent even if it's removed
getParent: function(){
return getParent(this);
},
getParentCollection: function(){
return Meteor.isClient ?
window[this.parent.collection] : global[this.parent.collection];
},
});
//when created, inherit parent properties
collection.after.insert(function(userId, doc){
inheritParentProperties(doc, collection);
});
collection.before.update(function(userId, doc, fieldNames, modifier, options){
//if we are restoring this asset, unmark that it was removed with its parent, we no longer care
if (modifier && modifier.$unset && modifier.$unset.removed) {
modifier.$unset.removedWith = "";
}
});
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
if (modifier && modifier.$set && modifier.$set["parent.id"]){
//when we change parents, inherit its properties
inheritParentProperties(doc, collection);
}
});
collection.softRemoveNode = collection.softRemoveNode || function(id){
collection.softRemove(id);
};
collection.restoreNode = collection.restoreNode || function(id){
collection.restore(id);
};
collection.attachSchema(childSchema);
childCollections.push(collection);
};
makeParent = function(collection, donatedKeys){
donatedKeys = joinWithDefaultKeys(donatedKeys);
var collectionName = collection._collection.name;
//after changing, push the changes to all children
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
modifier = limitModifierToKeys(modifier, donatedKeys);
doc = _.pick(doc, ["_id", "charId"]);
if (!modifier) return;
Meteor.call("updateChildren", doc, modifier, true);
});
collection.softRemoveNode = function(id){
Meteor.call("softRemoveNode", collectionName, id);
};
collection.restoreNode = function(id){
Meteor.call("restoreNode", collectionName, id);
};
if (Meteor.isServer) collection.after.remove(function(userId, doc) {
_.each(childCollections, function(collection){
collection.remove(
{"parent.id": doc._id}
);
});
});
};
var checkPermission = function(userId, charId){
var char = Characters.findOne(charId, {fields: {owner: 1, writers: 1}});
if (!char)
throw new Meteor.Error("Access Denied, no charId",
"Character " + charId + " does not exist");
if (!userId)
throw new Meteor.Error("Access Denied, no userId",
"No UserId set when trying to update character asset.");
if (char.owner !== userId && !_.contains(char.writers, userId))
throw new Meteor.Error("Access Denied, not permitted",
"Not permitted to update assets of this character.");
return true;
};
var cascadeSoftRemove = function(id, removedWithId){
_.each(childCollections, function(treeCollection){
treeCollection.update(
{"parent.id": id},
{$set: {
removed: true,
removedWith: removedWithId,
}},
{multi: true}
);
treeCollection.find({"parent.id": id}).forEach(function(doc){
cascadeSoftRemove(doc._id, removedWithId);
});
});
};
var checkRemovePermission = function(collectionName, id, self){
check(collectionName, String);
check(id, String);
var collection = Mongo.Collection.get(collectionName);
var node = collection.findOne(id);
var charId = node && node.charId;
checkPermission(self.userId, charId);
};
Meteor.methods({
softRemoveNode: function(collectionName, id){
checkRemovePermission(collectionName, id, this);
var collection = Mongo.Collection.get(collectionName);
collection.softRemove(id);
cascadeSoftRemove(id, id);
},
restoreNode: function(collectionName, id){
checkRemovePermission(collectionName, id, this);
var collection = Mongo.Collection.get(collectionName);
collection.restore(id);
_.each(childCollections, function(treeCollection){
treeCollection.update(
{removedWith: id, removed: true},
{$unset: {removed: true, removedWith: ""}},
{multi: true}
);
});
},
updateChildren: function(parent, modifier, limitToInheritance) {
check(parent, {_id: String, charId: String});
check(modifier, Object);
checkPermission(this.userId, parent.charId);
var selector = {"parent.id": parent._id};
_.each(childCollections, function(collection){
var thisModifier;
if (limitToInheritance){
thisModifier = limitModifierToKeys(modifier, collection.inheritedKeys);
} else {
thisModifier = _.clone(modifier);
}
if (_.isEmpty(thisModifier)) return;
collection.update(selector, thisModifier, {multi: true, removed: true});
});
},
cloneChildren: function(objectId, newParent){
check(objectId, String);
check(newParent, {id: String, collection: String});
_.each(childCollections, function(collection){
var keys = collection.simpleSchema().objectKeys();
collection.find({"parent.id": objectId}).forEach(function(doc){
var newDoc = _.pick(doc, keys);
newDoc.parent = newParent;
collection.insert(newDoc);
});
});
},
});

View File

@@ -0,0 +1,18 @@
canEditCharacter = function(charId, userId){
userId = userId || Meteor.userId();
var char = Characters.findOne(charId, {fields: {owner: 1, writers: 1}});
if (!char) return true;
return (userId === char.owner || _.contains(char.writers, userId));
};
canViewCharacter = function(charId, userId){
userId = userId || Meteor.userId();
var char = Characters.findOne(
charId,
{fields: {owner: 1, writers: 1, readers: 1}}
);
if (!char) return true;
return userId === char.owner ||
_.contains(char.writers, userId) ||
_.contains(char.readers, userId);
};

View File

@@ -0,0 +1,66 @@
pointBuyCost = {
"8": 0,
"9": 1,
"10": 2,
"11": 3,
"12": 4,
"13": 5,
"14": 7,
"15": 9,
};
var getPointBuyEffect = function(stat, value, pointsUsed, charId){
return {
modifier:{
charId: charId,
stat: stat,
name: pointsUsed + " Point Buy",
operation: "base",
value: value,
parent: {collection: "Characters", id: charId},
enabled: true,
},
selector:{
charId: charId,
stat: stat,
operation: "base",
},
};
};
var checkPermission = function(userId, charId){
var char = Characters.findOne(charId, {fields: {owner: 1, writers: 1}});
if (!char)
throw new Meteor.Error("Access Denied",
"Character " + charId + " does not exist");
if (!userId)
throw new Meteor.Error("Access Denied",
"No UserId set when trying to update character asset.");
if (char.owner !== userId && !_.contains(char.writers, userId))
throw new Meteor.Error("Access Denied",
"Not permitted to update assets of this character.");
return true;
};
Meteor.methods({
pointBuyAbilityScores: function(charId, points){
check(points, {
strength: Number,
dexterity: Number,
constitution: Number,
intelligence: Number,
wisdom: Number,
charisma: Number,
});
check(charId, String);
checkPermission(this.userId, charId);
var pointsUsed = 0;
_.each(points, function(value, key){
pointsUsed += pointBuyCost[value];
});
_.each(points, function(value, ability){
var pbEffect = getPointBuyEffect(ability, value, pointsUsed, charId);
Effects.upsert(pbEffect.selector, pbEffect.modifier);
});
}
});

View File

@@ -0,0 +1,35 @@
preventLoop = function(inputFunction){
var self = this;
if (!_.isFunction(inputFunction)){
throw new Meteor.Error(
"Not a function",
"preventLoop can only take a function as an argument"
);
}
//store a private array of arguments we have been given without returning
//if we try to visit the same argument twice before resolving its value
//we are in a dependency loop and need to GTFO
var visitedArgs = [];
return function(){
var result;
var hash = _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
//we're still evaluating this attribute, must be in a loop
if (_.contains(visitedArgs, hash)) {
console.warn("dependency loop detected");
return NaN;
} else {
//push this hash to the list of visited hashes
//we can't visit it again unless it returns first
visitedArgs.push(hash);
}
try {
result = inputFunction.apply(this, arguments);
} finally{
//this hash returns or fails, pull it from the array
visitedArgs = _.without(visitedArgs, hash);
}
return result;
};
};

View File

@@ -0,0 +1,14 @@
Meteor.methods({
"getUserId": function(username){
if (!username) return;
regex = new RegExp("^" + username + "$", "i")
var user = Meteor.users.findOne(
{$or: [
{username: username},
{"emails.address": regex},
{"services.google.email": regex},
]}
);
return user && user._id;
}
});

View File

@@ -0,0 +1,14 @@
updatePolymerInputs = function(self){
_.defer(function(){
//update all autogrows after they've been filled
var pata = self.$("paper-autogrow-textarea");
pata.each(function(index, el){
el.update($(el).children().get(0));
});
//update all input fields as well
var input = self.$("paper-input");
input.each(function(index, el){
el.valueChanged();
});
});
};

View File

@@ -0,0 +1,9 @@
vMixCharacter = function(charId){
return JSON.stringify([characterExport(charId)], null, 2);
};
vMixParty = function(partyId){
var party = Parties.findOne(partyId);
var chars = _.map(party.characters, charId => characterExport(charId));
return JSON.stringify(chars, null, 2);
};