Compare commits

...

89 Commits
0.5.4 ... 0.7.0

Author SHA1 Message Date
Stefan Zermatten
b578dd5fb0 Merge branch 'feature-carry-capacity-modifier' 2015-09-03 14:09:51 +02:00
Stefan Zermatten
5d6f934d88 Bumped version 2015-09-03 14:09:23 +02:00
Stefan Zermatten
337f0bfa8a Made sure migration touches every character 2015-09-03 14:09:17 +02:00
Stefan Zermatten
c62784894b Made sure encumbered conditions respect carry capacity 2015-09-03 13:53:00 +02:00
Stefan Zermatten
75fff43d7d Gave an effect menu option for carry capacity 2015-09-03 13:52:40 +02:00
Stefan Zermatten
a9eeeac0df Fixed carry capacity bar 2015-09-03 13:52:22 +02:00
Stefan Zermatten
c8af0ff0a9 Fixed carry capacity table 2015-09-03 13:52:06 +02:00
Stefan Zermatten
9e200db7b9 Made carry capacity an attribute, migrations need testing 2015-08-31 15:51:52 +02:00
Stefan Zermatten
c08cf83096 Bumped version 2015-08-27 12:21:23 +02:00
Stefan Zermatten
d9368b06d0 Merge branch 'bugfix-0.6.8' 2015-08-27 12:07:42 +02:00
Stefan Zermatten
2703367681 Proficiencies now get disabled when their features are disabled 2015-08-27 12:05:56 +02:00
Stefan Zermatten
d419442549 Fixed share dialog not finding usernames or email addresses 2015-08-27 11:59:38 +02:00
Stefan Zermatten
99df01c950 Prevented negative temporary hitpoints being added 2015-08-27 11:43:34 +02:00
Stefan Zermatten
d76349b3bb Merge branch 'release-0.6.7a' 2015-07-29 09:57:06 +02:00
Stefan Zermatten
39c061f4e8 Fixed formatting of not found page, now has a toolbar and centered text 2015-07-29 09:55:22 +02:00
Stefan Zermatten
6d167ddb22 Fixed formatting of loading page, more padding 2015-07-29 09:55:01 +02:00
Stefan Zermatten
037acbd459 Added Starter Set Wizard to the front page 2015-07-29 09:54:43 +02:00
Stefan Zermatten
4d3fc3bb09 Fixed accidental back() requests when closing detail dialogs 2015-07-29 09:54:18 +02:00
Stefan Zermatten
4b984d4fac Made analytics show all characters as the same page 2015-07-29 09:53:46 +02:00
Stefan Zermatten
58843613ba Merge branch 'release-0.6.7' 2015-07-27 13:12:42 +02:00
Stefan Zermatten
39b549b24b Bumped version 2015-07-27 13:12:28 +02:00
Stefan Zermatten
c79177de72 Added Google Analytics 2015-07-27 13:10:33 +02:00
Stefan Zermatten
11d09b1487 Fixed effect values not being visible on mobile 2015-07-27 12:16:02 +02:00
Stefan Zermatten
0e4918d57d Merge branch 'hotfix-style' 2015-07-27 12:08:22 +02:00
Stefan Zermatten
949f313af2 removed 1st column of all tables being 100px 2015-07-27 12:08:15 +02:00
Stefan Zermatten
85b63f152f Merge branch 'release-0.6.6' into develop 2015-07-27 11:23:12 +02:00
Stefan Zermatten
2141d52a7a Merge branch 'release-0.6.6' 2015-07-27 11:22:41 +02:00
Stefan Zermatten
4c84235064 Bumped version 2015-07-27 11:22:26 +02:00
Stefan Zermatten
0e194a5408 Added markdown to text areas 2015-07-27 10:21:26 +02:00
Stefan Zermatten
4b60eac330 Fixed typo on change log 2015-07-24 11:52:46 +02:00
Stefan Zermatten
cb017c359d Merge branch 'release-0.6.5' into develop 2015-07-24 11:31:05 +02:00
Stefan Zermatten
15aaaa5c14 Merge branch 'release-0.6.5' 2015-07-24 11:29:50 +02:00
Stefan Zermatten
290bee83b4 Bumped version 2015-07-24 11:29:03 +02:00
Stefan Zermatten
fcd2461205 Merge branch 'master' into release-0.6.5 2015-07-24 11:26:46 +02:00
Stefan Zermatten
52198d0249 Disabled edit buttons for read-only viewers 2015-07-23 15:07:39 +02:00
Stefan Zermatten
ba7ccfdfa0 Added support for character profile pictures 2015-07-23 15:04:43 +02:00
Stefan Zermatten
92d3b086fa net worth calculations now take into account your containers' values 2015-07-23 09:14:55 +02:00
Stefan Zermatten
e180595dcd Merge branch 'release-0.6.4' 2015-07-06 13:37:02 +02:00
Stefan Zermatten
ed708bdde0 bumped version 2015-07-06 13:36:50 +02:00
Stefan Zermatten
d7852d640f Fixed Math using min and max incorrectly in skills 2015-07-03 13:21:26 +02:00
Stefan Zermatten
a2fdee88b6 Ordered Character lists 2015-06-29 14:00:55 +02:00
Stefan Zermatten
af070b1578 Attacks inherit spell names correctly 2015-06-29 13:55:27 +02:00
Stefan Zermatten
83a8eeef0f Fixed editing of multiple attacks 2015-06-29 13:55:15 +02:00
Stefan Zermatten
34f8e7402b Hit dice constitution now has a + sign when positive 2015-06-29 10:21:49 +02:00
Stefan Zermatten
1b7e2cd850 Merge branch 'hotfix-rounding' into develop 2015-06-29 10:04:45 +02:00
Stefan Zermatten
463b7f0fc9 Merge branch 'hotfix-rounding' 2015-06-29 09:45:38 +02:00
Stefan Zermatten
266495abc8 Bumped version 2015-06-29 09:44:57 +02:00
Stefan Zermatten
453d4365d3 Prevent memoized values being used in dependency loops 2015-06-29 09:41:01 +02:00
Stefan Zermatten
e0ce6275bf Floor character attribute values 2015-06-29 09:40:37 +02:00
Stefan Zermatten
16b16ce6c6 Merge branch 'hotfix-minmax' into develop 2015-06-26 10:49:10 +02:00
Stefan Zermatten
80c72a274e Merge branch 'hotfix-minmax' 2015-06-26 10:48:43 +02:00
Stefan Zermatten
91f0f7954c Bumped version 2015-06-26 10:48:33 +02:00
Stefan Zermatten
c74abcb608 Fixed min and max error 2015-06-26 10:48:04 +02:00
Stefan Zermatten
da8b91594e Removed reference to character helper 2015-06-25 14:22:07 +02:00
Stefan Zermatten
fc26f5a73e Merge branch 'hotfix-experience-inputs' into develop 2015-06-25 14:20:54 +02:00
Stefan Zermatten
4f60766d5d Merge branch 'hotfix-experience-edit-inputs' 2015-06-25 14:07:34 +02:00
Stefan Zermatten
e992aeebef Bumped version 2015-06-25 14:07:24 +02:00
Stefan Zermatten
4108346a98 Fixed experience inputs updating 2015-06-25 14:07:13 +02:00
Stefan Zermatten
946fadadc2 Merge branch 'release-0.6.0' 2015-06-25 13:49:25 +02:00
Stefan Zermatten
d2cc2833a9 Merge branch 'release-0.6.0' into develop 2015-06-25 13:48:32 +02:00
Stefan Zermatten
af57326194 Bumped version 2015-06-25 13:48:12 +02:00
Stefan Zermatten
98c69e9e17 Style tweaks 2015-06-25 13:38:33 +02:00
Stefan Zermatten
395edd0563 Character settings now formatted and has done button 2015-06-25 13:38:20 +02:00
Stefan Zermatten
43e87e7786 Container weight summaries now round off 2015-06-25 13:37:55 +02:00
Stefan Zermatten
ad347504c6 Fixed experienceDialog updating polymer inputs 2015-06-25 13:37:03 +02:00
Stefan Zermatten
4e6e99b695 Fixed spellDialog formatting 2015-06-25 13:36:42 +02:00
Stefan Zermatten
104624a322 Merge branch 'feature-soft-memoize' into develop 2015-06-25 13:35:19 +02:00
Stefan Zermatten
79d166e6af Removed references to old calculations 2015-06-25 13:34:44 +02:00
Stefan Zermatten
86c934e8ac Began replacing calls to helpers with calls to memoized functions 2015-06-18 15:14:37 +02:00
Stefan Zermatten
a034cbf30e Duplicate character helpers with memoized functions not attached to characters 2015-06-18 13:34:59 +02:00
Stefan Zermatten
d5680ebf8a Add memoize functionality 2015-06-18 13:33:54 +02:00
Stefan Zermatten
53f2fcc945 Merge branch 'release-0.5.7' into develop 2015-06-17 14:32:35 +02:00
Stefan Zermatten
612e127be4 Merge branch 'release-0.5.7' 2015-06-17 14:22:33 +02:00
Stefan Zermatten
2b0d975cee change logs 2015-06-17 14:22:12 +02:00
Stefan Zermatten
248ab9bb6b Added moving item dialog, that can't actually be triggered 2015-06-17 14:15:49 +02:00
Stefan Zermatten
314ce85410 Attacks can now be added to spells 2015-06-17 09:05:23 +02:00
Stefan Zermatten
9ff45dbcc2 backgrounds now have proficiencies 2015-06-17 08:49:26 +02:00
Stefan Zermatten
6caf19bc99 Merge branch 'release-0.5.6' into develop 2015-06-15 13:36:50 +02:00
Stefan Zermatten
c2b04d0977 Merge branch 'release-0.5.6' 2015-06-15 13:34:29 +02:00
Stefan Zermatten
9012c4a558 change logs 2015-06-15 13:34:10 +02:00
Stefan Zermatten
65a84937f2 Merge branch 'feature-share-publically' into develop 2015-06-15 13:30:42 +02:00
Stefan Zermatten
eebb88b6b1 New front page and darker style 2015-06-15 13:29:33 +02:00
Stefan Zermatten
06ab7c5116 Merge branch 'master' into feature-share-publically 2015-06-15 10:04:40 +02:00
Stefan Zermatten
89f03c7601 Merge branch 'hotfix-gmail-report' 2015-06-12 08:04:23 +02:00
Stefan Zermatten
9d2eb14c0c Change logs 2015-06-12 08:03:46 +02:00
Stefan Zermatten
7b3cb54983 Added gmail email address senders to the report emails 2015-06-12 08:02:33 +02:00
Stefan Zermatten
a09bad2fed Change logs 2015-06-10 11:13:58 +02:00
Stefan Zermatten
034067bd6e Added link to example character 2015-06-10 10:58:47 +02:00
Stefan Zermatten
0d75cd5d15 Added sharing to anyone with link, changed home page 2015-06-09 17:06:51 +02:00
101 changed files with 1333 additions and 506 deletions

1
rpg-docs/.gitignore vendored
View File

@@ -1,5 +1,6 @@
.meteor/local
.meteor/meteorite
settings.json
public/components
nohup.out
dump

View File

@@ -27,3 +27,5 @@ fourseven:scss@2.1.1
wolves:bourbon
meteorhacks:subs-manager
meteorhacks:kadira
chuangbo:marked
reywood:iron-router-ga

View File

@@ -14,6 +14,7 @@ blaze-tools@1.0.3
boilerplate-generator@1.0.3
callback-hook@1.0.3
check@1.0.5
chuangbo:marked@0.3.5
coffeescript@1.0.6
dburles:collection-helpers@1.0.3
dburles:mongo-collection-instances@0.3.3
@@ -70,6 +71,7 @@ reactive-dict@1.1.0
reactive-var@1.0.5
reload@1.1.3
retry@1.0.3
reywood:iron-router-ga@0.6.0
routepolicy@1.0.5
service-configuration@1.0.4
session@1.1.0

View File

@@ -3,16 +3,17 @@ Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({
//strings
name: {type: String, defaultValue: "", trim: false},
alignment: {type: String, defaultValue: "", trim: false},
gender: {type: String, defaultValue: "", trim: false},
race: {type: String, defaultValue: "", trim: false},
description: {type: String, defaultValue: "", trim: false},
personality: {type: String, defaultValue: "", trim: false},
ideals: {type: String, defaultValue: "", trim: false},
bonds: {type: String, defaultValue: "", trim: false},
flaws: {type: String, defaultValue: "", trim: false},
backstory: {type: String, defaultValue: "", trim: false},
name: {type: String, defaultValue: "", trim: false, optional: true},
alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: {type: String, defaultValue: "", trim: false, optional: true},
race: {type: String, defaultValue: "", trim: false, optional: true},
picture: {type: String, defaultValue: "", trim: true, optional: true},
description: {type: String, defaultValue: "", trim: false, optional: true},
personality: {type: String, defaultValue: "", trim: false, optional: true},
ideals: {type: String, defaultValue: "", trim: false, optional: true},
bonds: {type: String, defaultValue: "", trim: false, optional: true},
flaws: {type: String, defaultValue: "", trim: false, optional: true},
backstory: {type: String, defaultValue: "", trim: false, optional: true},
//attributes
//ability scores
@@ -32,6 +33,7 @@ Schemas.Character = new SimpleSchema({
age: {type: Schemas.Attribute},
ageRate: {type: Schemas.Attribute},
armor: {type: Schemas.Attribute},
carryMultiplier: {type: Schemas.Attribute},
//resources
level1SpellSlots: {type: Schemas.Attribute},
@@ -176,36 +178,44 @@ Schemas.Character = new SimpleSchema({
"settings.useStandardEncumbrance": {type: Boolean, defaultValue: true},
//hide spellcasting
"settings.hideSpellcasting": {type: Boolean, defaultValue: false},
//show to anyone with link
"settings.viewPermission": {
type: String,
defaultValue: "whitelist",
allowedValues: ["whitelist", "public"],
},
});
Characters.attachSchema(Schemas.Character);
var attributeBase = function(charId, statName){
var attributeBase = preventLoop(function(charId, statName){
check(statName, String);
//if it's a damage multiplier, we treat it specially
if (_.contains(DAMAGE_MULTIPLIERS, statName)){
var effects = Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "mul"}
).fetch();
var resistCount = 0;
var vulnCount = 0;
var multiplierEvaluationFail = false;
_.each(effects, function(effect){
var val = evaluateEffect(charId, effect);
if (val === 0.5){ //resistance
resistCount += 1;
} else if (val === 2){ //vulnerability
vulnCount += 1;
} else if (val === 0){ //imunity
return 0; //imunity is absolute
} else {
multiplierEvaluationFail = true;
}
});
if (multiplierEvaluationFail){
//we can't work it out correctly, set the value to 1
//and try work it out using regular maths below
value = 1;
var invulnerabilityCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0,
}).count();
if (invulnerabilityCount) return 0;
var resistCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0.5,
}).count();
var vulnCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 2,
}).count();
if (!resistCount && !vulnCount){
return 1;
} else if (resistCount && !vulnCount){
return 0.5;
} else if (!resistCount && vulnCount){
@@ -214,61 +224,69 @@ var attributeBase = function(charId, statName){
return 1;
}
}
var value;
var base = 0;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
var value = 0;
//start with the highest base value
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "base"}
).forEach(function(effect){
var efv = evaluateEffect(charId, effect);
if (efv > value){
value = efv;
Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "base"){
if (value > base) base = value;
} else if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
}
});
//add all the add values
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "add"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
var result = (base + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
//multiply all the mul values
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "mul"}
).forEach(function(effect){
value *= evaluateEffect(charId, effect);
});
return Math.floor(result);
});
//ensure value is >= all mins
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "min"}
).forEach(function(effect){
var min = evaluateEffect(charId, effect);
value = value > min ? value : min;
if (Meteor.isClient) {
Template.registerHelper("characterCalculate", function(func, charId, input) {
try {
return Characters.calculate[func](charId, input);
} catch (e){
if (!Characters.calculate[func]){
throw new Error(func + "is not a function name");
} else {
throw e;
}
}
});
}
//ensure value is <= all maxes
Effects.find(
{charId: charId, stat: statName, enabled: true, operation: "max"}
).forEach(function(effect){
var max = evaluateEffect(charId, effect);
value = value < max ? value : max;
//create a local memoize with a argument concatenating hash function
var memoize = function(f) {
return Tracker.memoize(f, function() {
return _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
});
return value;
};
//functions and calculated values.
//These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters
Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector});
var char = Characters.findOne(charId, {fields: fieldSelector});
var field = char[fieldName];
if (field === undefined){
throw new Meteor.Error(
@@ -281,8 +299,7 @@ Characters.helpers({
}
return field;
},
//returns the value of a field
fieldValue : function(fieldName){
fieldValue: function(charId, fieldName) {
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
@@ -292,102 +309,92 @@ Characters.helpers({
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return this.skillMod(fieldName);
return Characters.calculate.skillMod(charId, fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return this.attributeValue(fieldName);
return Characters.calculate.attributeValue(charId, fieldName);
}
//fall back to just returning the field itself
return this.getField(fieldName);
return Characters.calculate.getField(charId, fieldName);
},
attributeValue: function(attributeName){
var charId = this._id;
var attribute = this.getField(attributeName);
attributeValue: memoize(function(charId, attributeName){
var attribute = Characters.calculate.getField(charId, attributeName);
//base value
var value = this.attributeBase(attributeName);
var value = Characters.calculate.attributeBase(charId, attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
},
attributeBase: preventLoop(function(attributeName){
var charId = this._id;
//base value
}),
attributeBase: memoize(function(charId, attributeName){
return attributeBase(charId, attributeName);
}),
skillMod: preventLoop(function(skillName){
var charId = this._id;
var skill = this.getField(skillName);
skillMod: memoize(preventLoop(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
var ability = Characters.calculate.attributeValue(charId, skill.ability);
//base modifier
var mod = +getMod(ability);
//multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skillName);
var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
mod += prof * Characters.calculate.attributeValue(charId, "proficiencyBonus");
//apply all effects
var rawEffects = Effects.find(
{charId: charId, stat: skillName, enabled: true}
).fetch();
var effects = _.groupBy(rawEffects, "operation");
_.forEach(effects.add, function(effect){
mod += evaluateEffect(charId, effect);
});
_.forEach(effects.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
_.forEach(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min ? mod : min;
});
_.forEach(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max ? mod : max;
});
return signedString(mod);
}),
var value;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
proficiency: function(skillName){
var charId = this._id;
Effects.find({
charId: charId,
stat: skillName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
}
});
var result = (mod + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
return Math.floor(result);
})),
proficiency: memoize(function(charId, skillName){
//return largest value in proficiency array
var prof = 0;
Proficiencies.find(
{charId: charId, name: skillName, enabled: true}
).forEach(function(proficiency){
var newProf = proficiency.value;
if (newProf > prof){
prof = newProf;
}
});
return prof;
},
passiveSkill: function(skillName){
if (_.isString(skillName)){
var skill = this.getField(skillName);
}
var charId = this._id;
var mod = +this.skillMod(skillName);
var prof = Proficiencies.findOne(
{charId: charId, name: skillName, enabled: true},
{sort: {value: -1}}
);
return prof && prof.value || 0;
}),
passiveSkill: memoize(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
var mod = +Characters.calculate.skillMod(charId, skillName);
var value = 10 + mod;
Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
return value;
//TODO decide whether (dis)advantage gives (-)+5 to passive checks
},
advantage: function(skillName){
var charId = this._id;
var advantage = Characters.calculate.advantage(charId, skillName);
value += 5 * advantage;
return Math.floor(value);
}),
advantage: memoize(function(charId, skillName){
var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"}
).count();
@@ -397,19 +404,18 @@ Characters.helpers({
if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1;
return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
},
abilityMod: function(attribute){
return signedString(getMod(this.attributeValue(attribute)));
},
passiveAbility: function(attribute){
var mod = +getMod(this.attributeValue(attribute));
passiveAbility: function(charId, attribute){
var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return 10 + mod;
},
xpLevel: function(){
var xp = this.experience();
xpLevel: function(charId){
var xp = Characters.calculate.experience(charId);
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
return i;
@@ -418,30 +424,103 @@ Characters.helpers({
if (xp > 355000) return 20;
return 0;
},
level: function(){
level: memoize(function(charId){
var level = 0;
Classes.find({charId: this._id}).forEach(function(cls){
Classes.find({charId: charId}).forEach(function(cls){
level += cls.level;
});
return level;
},
experience: function(){
}),
experience: memoize(function(charId){
var xp = 0;
Experiences.find(
{charId: this._id},
{charId: charId},
{fields: {value: 1}}
).forEach(function(e){
xp += e.value;
});
return xp;
}),
};
var depreciated = function() {
//var err = new Error("this function has been depreciated");
var name = "";
if (Template.instance()){
name = Template.instance().view.name;
}
var logString = "this function has been depreciated \n";
if (name){
logString += "View: " + name + "\n\n";
}
//logString += err.stack + "\n\n---------------------\n\n";
console.log(logString);
};
//functions and calculated values.
//These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters
Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
depreciated();
return Characters.calculate.getField(this._id, fieldName);
},
//returns the value of a field
fieldValue : function(fieldName){
depreciated();
return Characters.calculate.fieldValue(this._id, fieldName);
},
attributeValue: function(attributeName){
depreciated();
return Characters.calculate.attributeValue(this._id, attributeName);
},
attributeBase: function(attributeName){
depreciated();
return Characters.calculate.attributeBase(this._id, attributeName);
},
skillMod: function(skillName){
depreciated();
return Characters.calculate.skillMod(this._id, skillName);
},
proficiency: function(skillName){
depreciated();
return Characters.calculate.proficiency(this._id, skillName);
},
passiveSkill: function(skillName){
depreciated();
return Characters.calculate.passiveSkill(this._id, skillName);
},
advantage: function(skillName){
depreciated();
return Characters.calculate.advantage(this._id, skillName);
},
abilityMod: function(attribute){
depreciated();
return Characters.calculate.abilityMod(this._id, attribute);
},
passiveAbility: function(attribute){
depreciated();
return Characters.calculate.passiveAbility(this._id, attribute);
},
xpLevel: function(){
depreciated();
return Characters.calculate.xpLevel(this._id);
},
level: function(){
depreciated();
return Characters.calculate.level(this._id);
},
experience: function(){
depreciated();
return Characters.calculate.experience(this._id);
},
});
//clean up all data related to that character before removing it
Characters.after.remove(function(userId, character) {
if (Meteor.isServer){
if (Meteor.isServer){
Characters.after.remove(function(userId, character) {
Actions .remove({charId: character._id});
Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id});
@@ -454,8 +533,8 @@ Characters.after.remove(function(userId, character) {
SpellLists .remove({charId: character._id});
Items .remove({charId: character._id});
Containers .remove({charId: character._id});
}
});
});
}
Characters.allow({
insert: function(userId, doc) {

View File

@@ -107,6 +107,18 @@ if (Meteor.isServer) Characters.after.insert(function(userId, char) {
group: "Inate",
},
});
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
});
Effects.attachBehaviour("softRemovable");

View File

@@ -30,7 +30,7 @@ Schemas.Proficiency = new SimpleSchema({
Proficiencies.attachSchema(Schemas.Proficiency);
Proficiencies.attachBehaviour("softRemovable");
makeChild(Proficiencies);
makeChild(Proficiencies, ["enabled"]);
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -62,6 +62,7 @@ Spells.attachSchema(Schemas.Spell);
Spells.attachBehaviour("softRemovable");
makeChild(Spells); //children of spell lists
makeParent(Spells, ["name", "enabled"]); //parents of attacks
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
Spells.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -3,10 +3,6 @@
* Damage, healing and resource cost/recovery are all adjustments
*/
Schemas.Adjustment = new SimpleSchema({
name: {
type: String,
optional: true,
},
//which stat the adjustment is applied to
stat: {
type: String,

View File

@@ -3,9 +3,9 @@ TemporaryHitPoints = new Mongo.Collection("temporaryHitPoints");
Schemas.TemporaryHitPoints = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, optional: true},
maximum: {type: Number, defaultValue: 0},
used: {type: Number, defaultValue: 0},
deleteOnZero:{type: Boolean, defaultValue: true},
maximum: {type: Number, defaultValue: 0, min: 0, max: 500},
used: {type: Number, defaultValue: 0, min: 0, max: 500},
deleteOnZero:{type: Boolean, defaultValue: false},
dateAdded: {
type: Date,
autoValue: function() {

View File

@@ -51,6 +51,9 @@ Meteor.methods({
user.emails &&
user.emails[0] &&
user.emails[0].address ||
user.services &&
user.services.google &&
user.services.google.email ||
"reports@dicecloud.com";
var bodyText = "Report ID: " + id +
"\nSeverity: " + report.severity +

View File

@@ -1,20 +1,13 @@
Router.configure({
loadingTemplate: "loading",
layoutTemplate: "layout",
trackPageView: true,
});
Router.plugin("ensureSignedIn", {
except: [
"home",
"atSignIn",
"atSignUp",
"atForgotPassword",
"atResetPwd",
"atEnrollAccount",
"atVerifyEmail",
"atResendVerificationEmail",
"loginButtons",
"notFound",
only: [
"profile",
"characterList",
]
});
@@ -35,7 +28,7 @@ Router.map(function() {
},
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}});
return Characters.find({}, {fields: {_id: 1}, sort: {name: 1}});
}
},
onAfterAction: function() {
@@ -64,6 +57,12 @@ Router.map(function() {
document.title = name;
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/character");
this.next();
},
});
this.route("loading", {
@@ -93,4 +92,11 @@ Router.map(function() {
document.title = appName;
},
});
this.route("/guide", {
name: "guide",
onAfterAction: function() {
document.title = appName;
},
});
});

View File

@@ -77,10 +77,10 @@ this.GlobalUI = (function() {
var throttleBack = _.throttle(function() {
history.back();
}, 800, {trailing: false});
}, 100, {trailing: false});
GlobalUI.closeDetail = function() {
if (!!(window.history && window.history.pushState)) {
if (window.history && history.pushState && history.state.detail === "opened") {
throttleBack();
} else {
Session.set("global.ui.detailShow", false);

View File

@@ -1,6 +1,10 @@
Template.registerHelper("canEditCharacter", function(charId) {
return canEditCharacter(charId);
});
canEditCharacter = function(charId) {
var char = Characters.findOne(charId)
var userId = Meteor.userId();
return char.owner === userId ||
_.contains(char.writers, userId);
});
};

View File

@@ -20,6 +20,11 @@ openParentDialog = function(parent, charId, heroId) {
template: "itemDialog",
data: {itemId: parent.id},
};
} else if (parent.collection === "Spells") {
detail = {
template: "spellDialog",
data: {spellId: parent.id},
};
}
detail.heroId = heroId;
detail.charId = charId;

View File

@@ -91,6 +91,6 @@ $thinColumnWidth: 240px;
}
/* undo pointer cursor on detail box heading */
#globalDetail .card .top {
#globalDetail.card .top {
cursor: auto;
}

View File

@@ -70,8 +70,8 @@
background-color: #9E9E9E;
}
.blue-grey {
background-color: #607D8B;
.app-grey {
background-color: #424242;
}
.white {

View File

@@ -19,6 +19,21 @@ body {
background-color: #E0E0E0;
}
//fix tabs and core-toolbar having box shadow
core-toolbar {
box-shadow: none;
}
//give drawer panel a shadow always
core-header-panel[drawer] {
box-shadow: 2px 0px 5px 0px rgba(0,0,0,0.2);
}
//Paragraphs
p {
margin-bottom: 8px;
}
//Horizontal rule
hr {
background-color: #444;
@@ -27,7 +42,7 @@ hr {
color: #444;
height: 1px;
line-height: 0;
margin: 16px -16px;
margin: 16px 0;
text-align: center;
}

View File

@@ -1,8 +1,5 @@
td {
padding: 8px;
&:nth-child(1) {
min-width: 100px;
}
}
.strengthTable{
@@ -16,3 +13,9 @@ td {
}
}
}
.summaryTable {
&:nth-child(3){
text-align: right;
}
}

View File

@@ -3,26 +3,26 @@
<div layout vertical flex>
<div layout horizontal>
<!--attackBonus-->
<paper-input id="attackBonusInput"
<paper-input class="attackBonusInput"
label="Attack Bonus"
floatinglabel
value={{attackBonus}}
flex></paper-input>
<!--details-->
<paper-input id="detailInput"
<paper-input class="detailInput"
label="Details"
floatinglabel
value={{details}}></paper-input>
</div>
<div layout horizontal>
<!--damageBonus-->
<paper-input id="damageInput"
<paper-input class="damageInput"
label="Damage"
floatinglabel
value={{damage}}
flex></paper-input>
<!--DamageType-->
<paper-dropdown-menu id="damageTypeDropdown" label="Damage Type">
<paper-dropdown-menu class="damageTypeDropdown" label="Damage Type">
<paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{damageType}}>
{{#each damageTypes}}
@@ -34,6 +34,6 @@
</div>
</div>
<!--Delete Button-->
<paper-icon-button id="deleteAttack" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button>
<paper-icon-button class="deleteAttack" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button>
</div>
</template>

View File

@@ -15,23 +15,23 @@ var damageTypes = [
];
Template.attackEdit.events({
"tap #deleteAttack": function(event, instance) {
"tap .deleteAttack": function(event, instance) {
Attacks.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Attacks", "Attack");
},
"change #attackBonusInput": function(event) {
"change .attackBonusInput": function(event) {
var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {attackBonus: value}});
},
"change #damageInput": function(event) {
"change .damageInput": function(event) {
var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {damage: value}});
},
"change #detailInput": function(event) {
"change .detailInput": function(event) {
var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {details: value}});
},
"core-select #damageTypeDropdown": function(event) {
"core-select .damageTypeDropdown": function(event) {
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");

View File

@@ -1,7 +1,7 @@
<template name="characterSettings">
{{#with character}}
<div>
<table>
<div style="height: 100px;">
<table style="width: 100%;">
<tr>
<td>Hide Spells tab</td>
<td>
@@ -23,4 +23,5 @@
</table>
</div>
{{/with}}
<paper-button id="doneButton" affirmative> Done </paper-button>
</template>

View File

@@ -1,5 +1,17 @@
<template name="shareDialog">
<div style="width: 360px;">
<div layout horizontal center>
<div>Who can view this character: </div>
<paper-dropdown-menu class="visibilityDropdown"
label="Visibility">
<paper-dropdown layered class="dropdown">
<core-menu class="menu visibilityMenu" selected={{viewPermission}}>
<paper-item name="whitelist">Only people I share with</paper-item>
<paper-item name="public">Anyone with link</paper-item>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
</div>
<div>
{{#if readers.count}}
<div style="font-weight: 500;">
@@ -7,7 +19,7 @@
</div>
{{#each readers}}
<div layout horizontal center>
<div flex>{{username}}</div>
<div flex>{{getUserName}}</div>
<paper-icon-button class="deleteShare" icon="delete"></paper-icon-button>
</div>
{{/each}}

View File

@@ -3,6 +3,10 @@ Template.shareDialog.onCreated(function(){
});
Template.shareDialog.helpers({
viewPermission: function() {
var char = Characters.findOne(this._id, {fields: {settings: 1}});
return char.settings.viewPermission || "whitelist";
},
readers: function(){
var char = Characters.findOne(this._id, {fields: {readers: 1}});
return Meteor.users.find({_id: {$in: char.readers}});
@@ -19,9 +23,20 @@ Template.shareDialog.helpers({
return "User not found";
}
},
getUserName: function() {
return this.username || "user: " + this._id;
}
});
Template.shareDialog.events({
"core-select .visibilityDropdown": function(event){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");
var char = Characters.findOne(this._id, {fields: {settings: 1}});
if (value == char.settings.viewPermission) return;
Characters.update(this._id, {$set: {"settings.viewPermission": value}});
},
"input #userNameOrEmailInput":
function(event, instance){
var userName = instance.find("#userNameOrEmailInput").value;

View File

@@ -20,7 +20,7 @@ Template.characterSheet.helpers({
hideSpellcasting: function() {
var char = Characters.findOne(this._id);
return char && char.settings.hideSpellcasting;
}
},
});
Template.characterSheet.events({

View File

@@ -44,7 +44,8 @@
label="Value"
floatinglabel
value={{effectValue}}
flex>
flex
style="flex-basis: 100px;">
</paper-input>
</template>

View File

@@ -42,6 +42,7 @@ var stats = [
{stat: "rageDamage", name: "Rage Damage", group: "Stats"},
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
{stat: "level3SpellSlots", name: "level 3", group: "Spell Slots"},

View File

@@ -32,7 +32,7 @@
{{/if}}
{{#if description}}
<div class="pre-wrap">{{evaluateString charId description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}

View File

@@ -95,14 +95,16 @@
<core-tooltip label="Feature enabled"
position="left">
<paper-checkbox class="enabledCheckbox"
checked={{enabled}}>
checked={{enabled}}
disabled={{#unless canEditCharacter charId}}true{{/unless}}>
</paper-checkbox>
</core-tooltip>
{{/if}}
</div>
{{#if description}}
<div flex class="bottom text"
>{{evaluateString charId description}}</div>
<div flex class="bottom">
{{#markdown}}{{evaluateString charId shortDescription}}{{/markdown}}
</div>
{{/if}}
{{#if hasUses}}
<div layout horizontal center end-justified>
@@ -135,7 +137,7 @@
</template>
<template name="resource">
{{#if char.attributeBase name}}
{{#if characterCalculate "attributeBase" char._id name}}
<paper-shadow class="card"
hero-id="main" {{detailHero name char._id}}
layout horizontal>
@@ -152,7 +154,7 @@
disabled={{cantDecrement}}>
</paper-icon-button>
</div>
<div>{{char.attributeValue name}}</div>
<div>{{characterCalculate "attributeValue" char._id name}}</div>
<!--<div>/{{char.attributeBase name}}</div>-->
</div>
<div class="right clickable"

View File

@@ -3,14 +3,19 @@ Template.features.helpers({
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
return features;
},
shortDescription: function() {
if (_.isString(this.description)){
return this.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0];
}
},
hasUses: function(){
return this.usesValue() > 0;
},
noUsesLeft: function(){
return this.usesLeft() <= 0;
return this.usesLeft() <= 0 || !canEditCharacter(this.charId);
},
usesFull: function(){
return this.usesLeft() >= this.usesValue();
return this.usesLeft() >= this.usesValue() || !canEditCharacter(this.charId);
},
colorClass: function(){
return getColorClass(this.color);
@@ -96,16 +101,19 @@ Template.features.events({
Template.resource.helpers({
cantIncrement: function(){
var baseBigger = this.char.attributeValue(this.name) <
this.char.attributeBase(this.name);
return !baseBigger;
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
var baseBigger = value < base;
return !baseBigger || !canEditCharacter(this.char._id);
},
cantDecrement: function(){
var valuePositive = this.char.attributeValue(this.name) > 0;
return !valuePositive;
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var valuePositive = value > 0;
return !valuePositive || !canEditCharacter(this.char._id);
},
getColor: function(){
if (this.char.attributeValue(this.name) > 0){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
return this.color;
} else {
return "grey";
@@ -115,14 +123,17 @@ Template.resource.helpers({
Template.resource.events({
"tap .resourceUp": function(event){
if (this.char.attributeValue(this.name) < this.char.attributeBase(this.name)){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false});
}
},
"tap .resourceDown": function(event){
if (this.char.attributeValue(this.name) > 0){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false});

View File

@@ -13,8 +13,10 @@ var getFractionCarried = function(char) {
weight += item.totalWeight();
});
//get strength
var strength = char.attributeValue("strength");
var capacity = strength * 15;
var strength = Characters.calculate.attributeValue(char._id, "strength");
var carryMultiplier = Characters.calculate
.attributeValue(char._id, "carryMultiplier");
var capacity = strength * 15 * carryMultiplier;
return weight / capacity;
};

View File

@@ -34,13 +34,13 @@
<template name="containerView">
<div layout horizontal wrap center justified>
<table class="summaryTable fullwidth">
<tr><td>Container</td><td>{{weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
<tr><td>Container</td><td>{{round weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{round totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
</table>
</div>
{{#if description}}
<hr class="vertMargin">
<div class="pre-wrap">{{evaluateString charId description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
</template>

View File

@@ -35,8 +35,7 @@
<div class="item-slot">
<div class="item buff"
hero-id="main" {{detailHero}}
layout horizontal center
draggable="true">
layout horizontal center>
<div flex>
<core-icon icon="work"
style="margin-right: 16px">
@@ -114,6 +113,7 @@
</div>
<core-tooltip label="Container carried" position="left">
<paper-checkbox class="carriedCheckbox"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
checked={{isCarried}}>
</paper-checkbox>
</core-tooltip>
@@ -152,11 +152,11 @@
<div class="item {{hidden}} inventoryItem"
hero-id="main" {{detailHero}}
layout horizontal center
draggable="true">
draggable={{canEditCharacter charId}}>
<div flex class="itemName">
{{#if ne1 quantity}}{{quantity}}&nbsp;{{/if}}{{pluralName}}
</div>
{{#if settings.showIncrement}}
{{#if settings.showIncrement}}{{#if canEditCharacter charId}}
<div class="incrementButtons">
<paper-icon-button class="addItemQuantity"
icon="add"
@@ -166,7 +166,7 @@
icon="remove"
style="margin-right: -8px"></paper-icon-button>
</div>
{{/if}}
{{/if}}{{/if}}
</div>
</div>
</template>

View File

@@ -44,6 +44,12 @@ Template.inventory.helpers({
).forEach(function(item){
worth += item.totalValue();
});
Containers.find(
{charId: this._id},
{fields: {value : 1}}
).forEach(function(container) {
if (container.value) worth += container.value;
});
return worth;
},
weightCarried: function(){
@@ -174,6 +180,20 @@ Template.inventory.events({
heroId: itemId,
});
},
"hold .inventoryItem": function(event, instance) {
var itemId = this._id;
var charId = Template.parentData()._id;
var containerId = this.parent.id;
GlobalUI.showDialog({
template: "moveItemDialog",
data: {
charId: charId,
itemId: itemId,
containerId: containerId,
},
heading: "Move " + this.pluralName(),
});
},
"tap .incrementButtons": function(event) {
event.stopPropagation();
},

View File

@@ -18,8 +18,8 @@
{{#if requiresAttunement}}<div class="vertMargin">Requires Attunement</div>{{/if}}
</div>
{{#if description}}
<hr class="vertMargin">
<div class="pre-wrap">{{evaluateString charId description}}</div>
<hr style="margin: 16px 0 16px 0;">
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}

View File

@@ -0,0 +1,7 @@
html /deep/ .moveItemDialog paper-tabs::shadow #selectionBar {
background-color: #D50000;
}
html /deep/ .moveItemDialog paper-tab::shadow #ink {
color: #D50000;
}

View File

@@ -0,0 +1,49 @@
<template name="moveItemDialog">
<div class="moveItemDialog">
<paper-tabs selected="{{selectedTab}}">
<paper-tab name="containers"
class="clickable">
Containers
</paper-tab>
<paper-tab name="characters"
class="clickable">
Characters
</paper-tab>
</paper-tabs>
<core-animated-pages selected="{{selectedTab}}"
transitions="slide-from-right"
style="width: 250px;
height: 200px;
overflow-y: auto;
overflow-x: hidden;">
<section name="containers">
<core-menu id="containerMenu" style="margin: 0;">
{{#each containers}}
<paper-item name={{_id}}
layout horizontal center>
<core-icon icon="image:brightness-1"
style="color: {{hexColor color}};
margin-right: 16px;">
</core-icon>
<div>{{name}}</div>
</paper-item>
{{/each}}
</core-menu>
</section>
<section name="characters">
<core-menu id="characterMenu" style="margin: 0;">
{{#each characters}}
<paper-item name={{_id}}
layout horizontal center>
<div class="item small">
{{name}}
</div>
</paper-item>
{{/each}}
</core-menu>
</section>
</core-animated-pages>
</div>
<paper-button id="cancelButton" affirmative> Cancel </paper-button>
<paper-button id="moveButton" affirmative> Move </paper-button>
</template>

View File

@@ -0,0 +1,56 @@
Template.moveItemDialog.onCreated(function() {
Session.setDefault("moveItemDialogTab", "containers");
});
Template.moveItemDialog.helpers({
selectedTab: function() {
return Session.get("moveItemDialogTab");
},
characters: function() {
var userId = Meteor.userId();
return Characters.find(
{
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
],
_id: {$ne: this.charId},
},
{fields: {name: 1}}
);
},
containers: function(){
return Containers.find(
{
charId: this.charId,
_id: {$ne: this.containerId},
},
{
fields: {color: 1, name: 1},
sort: {color: 1, name: 1},
}
);
},
});
Template.moveItemDialog.events({
"tap paper-tab": function(event) {
Session.set("moveItemDialogTab", event.currentTarget.getAttribute("name"));
},
"tap #moveButton": function(event, instance) {
var tab = Session.get("moveItemDialogTab");
if (tab === "containers"){
var containerId = instance.find("#containerMenu").selected;
if (!containerId) throw "no menu selection";
Meteor.call("moveItemToContainer", this.itemId, containerId);
} else if (tab === "characters"){
var characterId = instance.find("#characterMenu").selected;
if (!characterId) throw "no menu selection";
Meteor.call("moveItemToCharacter", this.itemId, characterId);
} else {
throw "Move item dialog tab is not set to containers or character," +
" it is set to " + tab;
}
},
});

View File

@@ -6,9 +6,15 @@
</div>
{{#if description}}
<hr class="vertMargin">
<div class="pre-wrap">{{description}}</div>
<div>{{#markdown}}{{description}}{{/markdown}}</div>
{{/if}}
{{else}}
{{> experienceEdit}}
{{/baseDialog}}
{{/with}}
</template>
<template name="experienceEdit">
<div horizontal layout>
<!--Name-->
<paper-input id="experienceNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
@@ -23,6 +29,4 @@
<textarea id="experienceDescriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
{{/baseDialog}}
{{/with}}
</template>

View File

@@ -1,3 +1,7 @@
Template.experienceEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.experienceDialog.helpers({
experience: function(){
Experiences.findOne(this.experienceId);
@@ -18,8 +22,10 @@ Template.experienceDialog.events({
);
GlobalUI.closeDetail();
},
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #experienceNameInput, input #experienceNameInput": function(event){
});
Template.experienceEdit.events({
"change #experienceNameInput": function(event){
var value = event.currentTarget.value;
Experiences.update(this._id, {$set: {name: value}});
},

View File

@@ -9,8 +9,9 @@
hero-id="toolbar" {{detailHero}}
layout horizontal center>
<div flex>Experience</div>
<div >{{experience}} XP</div>
<paper-icon-button class="black54" id="addXP" icon="add"></paper-icon-button>
<div >{{characterCalculate "experience" _id}} XP</div>
<paper-icon-button class="black54" id="addXP" icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}></paper-icon-button>
</div>
<div class="bottom list">
{{#each experiences}}
@@ -45,7 +46,7 @@
layout horizontal center>
<div flex>
<div class="containerName subhead">
Level {{level}}
Level {{characterCalculate "level" _id}}
</div>
{{#if nextLevelXP}}
<div class="caption">
@@ -55,7 +56,8 @@
</div>
<paper-icon-button class="black54"
id="addClassButton"
icon="add">
icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
</paper-icon-button>
</div>
<div class="bottom list">
@@ -85,7 +87,7 @@
layout horizontal center>
{{name}}
</div>
<div class="bottom text">{{description}}</div>
<div class="bottom">{{#markdown}}{{description}}{{/markdown}}</div>
</paper-shadow>
{{/each}}
</div>

View File

@@ -41,7 +41,7 @@ Template.journal.helpers({
return Levels.find({charId: charId, classId: this._id}, {sort: {value: 1}});
},
nextLevelXP: function(){
var currentLevel = this.level();
var currentLevel = Characters.calculate.level(this._id);
if (currentLevel < 20){
return XP_TABLE[currentLevel];
}

View File

@@ -1,7 +1,7 @@
<template name="noteDialog">
{{#with note}}
{{#baseDialog title=name class=colorClass startEditing=../startEditing}}
<div class="pre-wrap">{{description}}</div>
<div>{{#markdown}}{{description}}{{/markdown}}</div>
{{else}}
{{> noteDialogEdit}}
{{/baseDialog}}

View File

@@ -0,0 +1,9 @@
<template name="backgroundDialog">
{{#baseDialog title=title class=colorClass hideColor="true" hideDelete="true"}}
<div>{{#markdown}}{{evaluateString charId value}}{{/markdown}}</div>
{{> proficiencyViewList charId=charId parentId=charId parentGroup="background"}}
{{else}}
{{> textDialogEdit}}
{{> proficiencyEditList parentId=charId parentCollection="Characters" charId=charId parentGroup="background"}}
{{/baseDialog}}
</template>

View File

@@ -0,0 +1,8 @@
Template.backgroundDialog.helpers({
value: function(){
var fieldSelector = {fields: {}};
fieldSelector.fields[this.field] = 1;
var char = Characters.findOne(this.charId, fieldSelector);
return char[this.field];
}
});

View File

@@ -1,18 +1,36 @@
<template name="personaDetailsDialog">
{{#baseDialog title=name class="deep-purple white-text" hideColor="true" hideDelete="true" startEditing=startEditing}}
{{alignment}} {{gender}} {{race}}
{{#with char}}
<div>{{alignment}} {{gender}} {{race}}</div>
<core-image style="width: 350px; height: 350px; margin-top: 8px;"
sizing="cover"
hero-id="image" hero
src={{picture}}></core-image>
{{/with}}
{{else}}
{{#with char}}
{{> personaDetailsEdit}}
{{/with}}
{{/baseDialog}}
</template>
<template name="personaDetailsEdit">
<div layout horizontal center-justified>
<div flex style="max-width: 350px;" layout vertical>
<!--Name-->
<paper-input id="nameInput" label="Name" floatinglabel value={{name}}></paper-input><br>
<paper-input id="nameInput" label="Name" floatinglabel value={{name}}></paper-input>
<!--Alignment-->
<paper-input id="alignmentInput" label="Alignment" floatinglabel value={{alignment}}></paper-input><br>
<paper-input id="alignmentInput" label="Alignment" floatinglabel value={{alignment}}></paper-input>
<!--Gender-->
<paper-input id="genderInput" label="Gender" floatinglabel value={{gender}}></paper-input><br>
<paper-input id="genderInput" label="Gender" floatinglabel value={{gender}}></paper-input>
<!--Race-->
<paper-input id="raceInput" label="Race" floatinglabel value={{race}}></paper-input><br>
<paper-input id="raceInput" label="Race" floatinglabel value={{race}}></paper-input>
<!--Picture-->
<paper-input id="pictureInput" label="Picture URL" floatinglabel value={{picture}}></paper-input>
<core-image style="height:350px; width: 100%; margin-top: 8px;"
sizing="cover"
hero-id="image" hero
src={{picture}}></core-image>
</div>
</div>
</template>

View File

@@ -2,21 +2,34 @@ Template.personaDetailsEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.personaDetailsDialog.helpers({
char: function() {
return Characters.findOne(
this._id,
{fields: {name: 1, alignment: 1, gender: 1, race: 1, picture: 1}}
);
}
});
Template.personaDetailsEdit.events({
"change #nameInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {name: input}});
Characters.update(this._id, {$set: {name: input}});
},
"change #alignmentInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {alignment: input}});
Characters.update(this._id, {$set: {alignment: input}});
},
"change #genderInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {gender: input}});
Characters.update(this._id, {$set: {gender: input}});
},
"change #raceInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {race: input}});
Characters.update(this._id, {$set: {race: input}});
},
"change #pictureInput": function(event){
var input = event.currentTarget.value;
Characters.update(this._id, {$set: {picture: input}});
},
});

View File

@@ -3,7 +3,35 @@
<div id="persona" class="scroll-y" fit>
<div class="column-container">
{{#with characterDetails}}
{{#containerCardHelper this}}{{alignment}} {{gender}} {{race}}{{/containerCardHelper}}
<paper-shadow class="card"
hero-id="main" {{detailHero "details" _id}}>
{{#unless picture}}
<div class="top subhead characterField {{colorClass}}"
hero-id="toolbar" {{detailHero "details" _id}}>
<div class="subhead" flex
hero-id="title" {{detailHero "details" _id}}>
{{name}}
</div>
</div>
{{else}}
<core-image class="characterField clickable"
style="height:350px; width: 100%;
background-color: #e8e8e8;"
sizing="cover"
hero-id="image" {{detailHero "details" _id}}
src={{picture}}></core-image>
{{/unless}}
<div class="bottom">
{{#if picture}}
<div class="title" hero-id="title" {{detailHero "details" _id}}>
{{name}}
</div>
{{/if}}
<div class="subhead">
{{alignment}} {{gender}} {{race}}
</div>
</div>
</paper-shadow>
{{/with}}
{{> containerCard characterField "description" "Description"}}
{{> containerCard characterField "personality" "Personality Traits"}}
@@ -40,6 +68,6 @@
{{title}}
</div>
</div>
<div class="bottom text">{{> UI.contentBlock}}</div>
<div class="bottom">{{#markdown}}{{> UI.contentBlock}}{{/markdown}}</div>
</paper-shadow>
</template>

View File

@@ -11,12 +11,12 @@ Template.persona.helpers({
characterDetails: function(){
var char = Characters.findOne(
this._id,
{fields: {name: 1, gender: 1, alignment: 1, race:1}}
{fields: {name: 1, gender: 1, alignment: 1, race:1, picture: 1}}
);
char.field = "details";
char.title = char.name;
char.color = "d";
char.topClass = "characterField";
char.startEditing = true;
return char;
},
characterField: function(field, title){
@@ -40,25 +40,28 @@ Template.persona.helpers({
Template.persona.events({
"tap .characterField": function(event){
if (this.field !== "details"){
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "textDialog",
data: {
charId: charId,
field: this.field,
title: this.title,
color: this.color,
},
heroId: this._id + this.field,
});
} else {
if (this.field == "details"){
this.charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "personaDetailsDialog",
data: this,
heroId: this._id + this.field,
});
} else {
var template = "textDialog";
if (this.field === "backstory") template = "backgroundDialog";
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: template,
data: {
charId: charId,
field: this.field,
title: this.title,
color: this.color,
startEditing: true,
},
heroId: this._id + this.field,
});
}
}
});

View File

@@ -1,6 +1,6 @@
<template name="textDialog">
{{#baseDialog title=title class=colorClass hideColor="true" hideDelete="true" startEditing=startEditing}}
<div class="pre-wrap">{{evaluateString charId value}}</div>
<div>{{#markdown}}{{evaluateString charId value}}{{/markdown}}</div>
{{else}}
{{> textDialogEdit}}
{{/baseDialog}}

View File

@@ -1,6 +1,10 @@
<template name="proficiencyView">
<div class="proficiencyView" layout horizontal center>
<core-icon icon="{{profIcon}}"></core-icon>
<div class="sideMargin">{{getName}}</div>
<div class="proficiencyView item small"
style="padding: 0;"
layout horizontal center>
<core-icon icon="{{profIcon}}" style="margin-right: 16px;"></core-icon>
<div>{{getName}}</div>
</div>
</template>

View File

@@ -2,7 +2,7 @@
{{#if proficiencies.count}}
<hr class="vertMargin">
<div class="proficiencies">
<h2 class="spaceAfter">Proficiencies</h2>
<h2 style="margin-bottom: 8px;">Proficiencies</h2>
{{#each proficiencies}}
{{> proficiencyView}}
{{/each}}

View File

@@ -9,24 +9,33 @@
</template>
<template name="spellDetails">
<div class="caption">
<div class="body2">
Level {{level}} {{school}}, {{preparedString}}
</div>
<div class="vertMargin">
<div style="margin: 16px 0 16px 0;">
{{#if castingTime}}
<div>
<span class="body2">Casting Time: </span><span>{{castingTime}}</span>
</div>
{{/if}}
{{#if range}}
<div>
<span class="body2">Range: </span><span>{{range}}</span>
</div>
{{/if}}
{{#if getComponents}}
<div>
<span class="body2">Components: </span><span>{{getComponents}}</span>
</div>
{{/if}}
{{#if duration}}
<div>
<span class="body2">Duration: </span><span>{{duration}}</span>
</div>
{{/if}}
</div>
<div class="pre-wrap">{{evaluateString charId description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{> attacksViewList charId=charId parentId=_id}}
</template>
<template name="spellEdit">
@@ -126,4 +135,5 @@
<textarea id="descriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name}}
</template>

View File

@@ -20,7 +20,7 @@
{{/if}}
</div>
<hr class="vertMargin">
<div class="pre-wrap">{{evaluateString charId description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
</div>
{{else}}
<!--Name-->

View File

@@ -72,6 +72,7 @@
<core-tooltip label="Change prepared spells"
position="left">
<paper-icon-button class="prepSpells"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
icon="book">
</paper-icon-button>
</core-tooltip>

View File

@@ -84,39 +84,35 @@ Template.spells.helpers({
},
cantCast: function(level, char){
for (var i = level; i <= 9; i++){
if (char.attributeValue("level" + i + "SpellSlots") > 0){
if (Characters.calculate.attributeValue(char._id, "level" + i + "SpellSlots") > 0){
return false;
}
}
return true;
},
baseSlots: function(char){
return char.attributeBase("level" + this.level + "SpellSlots");
},
slots: function(char){
return char.attributeValue("level" + this.level + "SpellSlots");
},
showSlots: function(char){
return this.level && char.attributeBase("level" + this.level + "SpellSlots");
return this.level && Characters.calculate.attributeBase(
char._id, "level" + this.level + "SpellSlots"
);
},
hasSlots: function(){
for (var i = 1; i <= 9; i += 1){
if (this.attributeBase("level" + i + "SpellSlots")){
if (Characters.calculate.attributeBase(this._id, "level" + i + "SpellSlots")){
return true;
}
}
return false;
},
slotBubbles: function(char){
var baseSlots = char.attributeBase("level" + this.level + "SpellSlots");
var currentSlots = char.attributeValue("level" + this.level + "SpellSlots");
var baseSlots = Characters.calculate.attributeBase(char._id, "level" + this.level + "SpellSlots");
var currentSlots = Characters.calculate.attributeValue(char._id, "level" + this.level + "SpellSlots");
var slotsUsed = baseSlots - currentSlots;
var bubbles = [];
var i;
for (i = 0; i < currentSlots; i++){
bubbles.push({
icon: "radio-button-on",
disabled: i !== currentSlots - 1, //last full slot not disabled
disabled: i !== currentSlots - 1 || !canEditCharacter(char._id), //last full slot not disabled
attribute: "level" + this.level + "SpellSlots",
charId: char._id,
});
@@ -124,7 +120,7 @@ Template.spells.helpers({
for (i = 0; i < slotsUsed; i++){
bubbles.push({
icon: "radio-button-off",
disabled: i !== 0, //first empty slot not disabled
disabled: i !== 0 || !canEditCharacter(char._id), //first empty slot not disabled
attribute: "level" + this.level + "SpellSlots",
charId: char._id,
});
@@ -143,15 +139,15 @@ Template.spells.events({
var char = Characters.findOne(this.charId);
if (event.currentTarget.icon === "radio-button-off"){
if (
char.attributeValue(this.attribute) <
char.attributeBase(this.attribute)
Characters.calculate.attributeValue(char._id, this.attribute) <
Characters.calculate.attributeBase(char._id, this.attribute)
){
modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = 1;
Characters.update(this.charId, modifier, {validate: false});
}
} else {
if (char.attributeValue(this.attribute) > 0){
if (Characters.calculate.attributeValue(char._id, this.attribute) > 0){
modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = -1;
Characters.update(this.charId, modifier, {validate: false});

View File

@@ -4,8 +4,8 @@
layout horizontal>
<div class="left white-text {{color}}"
hero-id="toolbar" {{detailHero ability ../_id}}>
<div class="display1">{{../attributeValue ability}}</div>
<div class="title">{{../abilityMod ability}}</div>
<div class="display1">{{characterCalculate "attributeValue" ../_id ability}}</div>
<div class="title">{{abilityMod}}</div>
</div>
<div class="right subhead" layout horizontal center>
{{title}}

View File

@@ -0,0 +1,9 @@
Template.abilityMiniCard.helpers({
abilityMod: function() {
return signedString(
Characters.calculate.abilityMod(
Template.parentData()._id, this.ability
)
);
}
});

View File

@@ -1,9 +1,11 @@
Template.addTHPDialog.events({
"tap #addButton": function(event, instance){
var max = +instance.find("#quantityInput").value;
if (!max || max < 0) max = 0;
TemporaryHitPoints.insert({
charId: this.charId,
name: instance.find("#nameInput").value,
maximum: +instance.find("#quantityInput").value,
maximum: max,
deleteOnZero: !!instance.find("#deleteWhenZeroCheckbox").checked,
});
}

View File

@@ -106,10 +106,8 @@ Template.attributeDialogView.helpers({
return a || b || c;
},
adjustment: function(){
var char = Characters.findOne(this.charId);
if (!char) return;
var value = char.attributeValue(this.statName);
var base = char.attributeBase(this.statName);
var value = Characters.calculate.attributeValue(this.charId, this.statName);
var base = Characters.calculate.attributeBase(this.charId, this.statName);
return value - base;
},
baseEffects: function(){
@@ -138,14 +136,10 @@ Template.attributeDialogView.helpers({
);
},
attributeBase: function(){
var char = Characters.findOne(this.charId);
if (!char) throw "character is " + char;
return char.attributeBase(this.statName);
return Characters.calculate.attributeBase(this.charId, this.statName);
},
attributeValue: function() {
var char = Characters.findOne(this.charId);
if (!char) throw "character is " + char;
return char.attributeValue(this.statName);
return Characters.calculate.attributeValue(this.charId, this.statName);
},
sourceName: function(){
if (this.parent.group === "racial"){

View File

@@ -2,26 +2,26 @@
<table class="carryCapacityTable strengthTable">
<tr>
<td>Encumbered</td>
<td>&gt;{{evaluate charId "strength * 5"}}lbs</td>
<td>&gt;{{evaluate charId "strength * 5 * carryMultiplier"}}lbs</td>
<td class="caption">Variant rule, encumbered characters move 10 feet slower</td>
</tr>
<tr>
<td>Heavily encumbered</td>
<td>&gt;{{evaluate charId "strength * 10"}}lbs</td>
<td>&gt;{{evaluate charId "strength * 10 * carryMultiplier"}}lbs</td>
<td class="caption">
Variant rule, heavily encumbered characters move 20 feet slower and have disadvantage on ability checks, attack rolls, and saving thows that use Strength, Dexterity, or Constitution
</td>
</tr>
<tr>
<td>Over Encumbered</td>
<td>&gt;{{evaluate charId "strength * 15"}}lbs</td>
<td>&gt;{{evaluate charId "strength * 15 * carryMultiplier"}}lbs</td>
<td class="caption">
Characters that can only just lift, push or drag their current load can only move at 5 feet.
</td>
</tr>
<tr>
<td>Push, drag or lift maximum</td>
<td>{{evaluate charId "strength * 30"}}lbs</td>
<td>{{evaluate charId "strength * 30 * carryMultiplier"}}lbs</td>
<td class="caption"></td>
</tr>
</table>

View File

@@ -1,4 +1,4 @@
.healthCard paper-slider{
.healthCard paper-diff-slider{
width: 100%;
margin-right: 8px;
}

View File

@@ -6,38 +6,55 @@
hero-id="toolbar" {{detailHero "hitPoints" _id}}
layout vertical center center-justified>
<div class="hitPointTitle clickable">Hit Points</div>
<paper-icon-button class="white54" id="addTempHP" icon="add"></paper-icon-button>
<paper-icon-button class="white54"
id="addTempHP"
icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
</paper-icon-button>
</div>
<div class="right" flex layout vertical center-justified style="min-width: 180px;">
<div layout horizontal>
<paper-slider id="hitPointSlider"
value={{attributeValue "hitPoints"}}
max={{attributeBase "hitPoints"}}
<paper-diff-slider id="hitPointSlider"
value={{characterCalculate "attributeValue" _id "hitPoints"}}
max={{characterCalculate "attributeBase" _id "hitPoints"}}
editable pin
role="slider"
></paper-slider>
disabled={{#unless canEditCharacter _id}}true{{/unless}}
role="slider">
</paper-diff-slider>
</div>
{{#each tempHitPoints}}
<div>
{{name}}
<div layout horizontal>
<paper-slider class="tempHitPointSlider"
<paper-diff-slider class="tempHitPointSlider"
value={{left}}
max={{maximum}}
editable pin
role="slider"
flex
></paper-slider>
{{#unless left}}{{#unless deleteOnZero}}
></paper-diff-slider>
{{#unless left}}
<paper-icon-button class="deleteTHP" icon="delete"></paper-icon-button>
{{/unless}}{{/unless}}
{{/unless}}
</div>
</div>
{{/each}}
<div class="caption">
{{#if multipliers.immunities.length}} <div>Immune: {{#each multipliers.immunities}} {{name}} {{/each}}</div>{{/if}}
{{#if multipliers.resistances.length}}<div>Resistance: {{#each multipliers.resistances}} {{name}} {{/each}}</div>{{/if}}
{{#if multipliers.weaknesses.length}} <div>Weakness: {{#each multipliers.weaknesses}} {{name}} {{/each}}</div>{{/if}}
{{#if multipliers.immunities.length}}
<div>
Immune: {{#each multipliers.immunities}} {{name}} {{/each}}
</div>
{{/if}}
{{#if multipliers.resistances.length}}
<div>
Resistance: {{#each multipliers.resistances}} {{name}} {{/each}}
</div>
{{/if}}
{{#if multipliers.weaknesses.length}}
<div>
Weakness: {{#each multipliers.weaknesses}} {{name}} {{/each}}
</div>
{{/if}}
</div>
{{#if showDeathSave}}
{{#with deathSaveObject}}

View File

@@ -3,7 +3,7 @@ Template.healthCard.helpers({
return TemporaryHitPoints.find({charId: this._id});
},
showDeathSave: function(){
return this.attributeValue("hitPoints") <= 0;
return Characters.calculate.attributeValue(this._id, "hitPoints") <= 0;
},
deathSaveObject: function(){
var char = Characters.findOne(this._id, {fields: {deathSave: 1}});
@@ -27,21 +27,20 @@ Template.healthCard.helpers({
return this.fail >= 3;
},
multipliers: function(){
var char = Characters.findOne(this._id, {fields: {_id: 1}});
var multipliers = [
{name: "Acid", value: char.attributeValue("acidMultiplier", 1)},
{name: "Bludgeoning", value: char.attributeValue("bludgeoningMultiplier", 1)},
{name: "Cold", value: char.attributeValue("coldMultiplier", 1)},
{name: "Fire", value: char.attributeValue("fireMultiplier", 1)},
{name: "Force", value: char.attributeValue("forceMultiplier", 1)},
{name: "Lightning", value: char.attributeValue("lightningMultiplier", 1)},
{name: "Necrotic", value: char.attributeValue("necroticMultiplier", 1)},
{name: "Piercing", value: char.attributeValue("piercingMultiplier", 1)},
{name: "Poison", value: char.attributeValue("poisonMultiplier", 1)},
{name: "Psychic", value: char.attributeValue("psychicMultiplier", 1)},
{name: "Radiant", value: char.attributeValue("radiantMultiplier", 1)},
{name: "Slashing", value: char.attributeValue("slashingMultiplier", 1)},
{name: "Thunder", value: char.attributeValue("thunderMultiplier", 1)},
{name: "Acid", value: Characters.calculate.attributeValue(this._id, "acidMultiplier")},
{name: "Bludgeoning", value: Characters.calculate.attributeValue(this._id, "bludgeoningMultiplier")},
{name: "Cold", value: Characters.calculate.attributeValue(this._id, "coldMultiplier")},
{name: "Fire", value: Characters.calculate.attributeValue(this._id, "fireMultiplier")},
{name: "Force", value: Characters.calculate.attributeValue(this._id, "forceMultiplier")},
{name: "Lightning", value: Characters.calculate.attributeValue(this._id, "lightningMultiplier")},
{name: "Necrotic", value: Characters.calculate.attributeValue(this._id, "necroticMultiplier")},
{name: "Piercing", value: Characters.calculate.attributeValue(this._id, "piercingMultiplier")},
{name: "Poison", value: Characters.calculate.attributeValue(this._id, "poisonMultiplier")},
{name: "Psychic", value: Characters.calculate.attributeValue(this._id, "psychicMultiplier")},
{name: "Radiant", value: Characters.calculate.attributeValue(this._id, "radiantMultiplier")},
{name: "Slashing", value: Characters.calculate.attributeValue(this._id, "slashingMultiplier")},
{name: "Thunder", value: Characters.calculate.attributeValue(this._id, "thunderMultiplier")},
];
multipliers = _.groupBy(multipliers, "value");
return {
@@ -55,7 +54,8 @@ Template.healthCard.helpers({
Template.healthCard.events({
"change #hitPointSlider": function(event){
var value = event.currentTarget.value;
var adjustment = value - this.attributeBase("hitPoints");
var base = Characters.calculate.attributeBase(this._id, "hitPoints")
var adjustment = value - base;
Characters.update(this._id, {$set: {"hitPoints.adjustment": adjustment}});
//reset the death saves if we are gaining HP
if (value > 0)

View File

@@ -1,5 +1,5 @@
<template name="hitDice">
{{#if ../attributeBase name}}
{{#if characterCalculate "attributeBase" ../_id name}}
<paper-shadow class="card hit-dice" hero-id="main"
{{detailHero name ../_id}}
layout horizontal>
@@ -18,10 +18,10 @@
</div>
<div class="resourceValue" layout vertical center>
<div>
{{../attributeValue name}}
{{characterCalculate "attributeValue" ../_id name}}
</div>
<div class="title white-text">
d{{diceNum}} {{../abilityMod "constitution"}}
d{{diceNum}} {{conMod}}
</div>
</div>
</div>

View File

@@ -1,25 +1,33 @@
Template.hitDice.helpers({
cantIncrement: function(){
var valueSmallerThanBase = this.char.attributeValue(this.name) <
this.char.attributeBase(this.name);
return !valueSmallerThanBase;
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
return value >= base || !canEditCharacter(this.char._id);
},
cantDecrement: function(){
var valuePositive = this.char.attributeValue(this.name) > 0;
return !valuePositive;
var value = Characters.calculate.attributeValue(this.char._id, this.name);
return value <= 0 || !canEditCharacter(this.char._id);
},
conMod: function(){
return signedString(
Characters.calculate.abilityMod(this.char._id, "constitution")
);
},
});
Template.hitDice.events({
"tap .resourceUp": function(event){
if (this.char.attributeValue(this.name) < this.char.attributeBase(this.name)){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false});
}
},
"tap .resourceDown": function(event){
if (this.char.attributeValue(this.name) > 0){
var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false});

View File

@@ -1,4 +1,4 @@
<!-- needs name, char, and skillName -->
<!-- needs name, charId, and skillName -->
<template name="skillDialog">
{{#baseDialog title=name class=color hideEdit=true}}
{{> skillDialogView}}
@@ -8,7 +8,7 @@
<template name="skillDialogView">
<div layout vertical center>
<div class="display2">
{{char.skillMod skillName}}
{{characterCalculate "skillMod" charId skillName}}
</div>
<div class="subhead">
<core-icon icon="{{profIcon}}" class="black54"></core-icon>
@@ -25,9 +25,9 @@
<table class="summaryTable">
<tr>
<td>{{abilityName}}</td>
<td>{{char.abilityMod ability}}</td>
<td>{{characterCalculate "abilityMod" charId ability}}</td>
</tr>
{{#if char.proficiency skillName}}
{{#if characterCalculate "proficiency" charId skillName}}
<tr>
<td>{{proficiencyValue}}</td>
<td>{{signedString profBonus}}</td>
@@ -59,7 +59,7 @@
{{/each}}
<tr class="body2">
<td>Total</td>
<td>{{char.skillMod skillName}}</td>
<td>{{characterCalculate "skillMod" charId skillName}}</td>
</tr>
</table>

View File

@@ -106,9 +106,7 @@ Template.skillDialogView.helpers({
return a || b || c;
},
profIcon: function(){
var char = Characters.findOne(this.charId);
if (!char) return;
var prof = char.proficiency(this.skillName);
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album";
@@ -123,13 +121,13 @@ Template.skillDialogView.helpers({
profBonus: function(){
var char = Characters.findOne(this.charId);
if (!char) return;
return char.proficiency(this.skillName) *
char.attributeValue("proficiencyBonus");
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
var proficiencyBonus =
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
return prof * proficiencyBonus;
},
proficiencyValue: function(){
var char = Characters.findOne(this.charId);
if (!char) return;
var prof = char.proficiency(this.skillName);
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
if (prof == 0.5) return "Half Proficiency";
if (prof == 1) return "Proficient";
if (prof == 2) return "Double Proficiency";
@@ -199,20 +197,21 @@ Template.skillDialogView.helpers({
return skill.ability;
},
abilityName: function(){
var opts = {fields: {}};
opts.fields[this.skillName] = 1;
var char = Characters.findOne(this.charId, opts);
if (!char) return;
var skill = char[this.skillName];
var skill = Characters.calculate.getField(this.charId, this.skillName);
if (!skill) return;
var ability = skill.ability;
return abilities[ability] && abilities[ability].name;
},
char: function(){
return Characters.findOne(this.charId, {fields:{_id: 1}});
},
sourceName: function(){
if (this.parent.collection === "Characters") return "inate";
if (this.parent.collection === "Characters"){
if (this.parent.group === "racial"){
return Characters.calculate.getField(this.charId, "race") || "Race";
}
if (this.parent.group === "background"){
return "Background";
}
return "Innate";
}
return this.getParent().name;
},
operationName: function(){

View File

@@ -8,7 +8,9 @@
{{#if failSkill}}
<div class="fail skill-mod">fail</div>
{{else}}
<div class="{{advantage}} skill-mod">{{../skillMod skill}}</div>
<div class="{{advantage}} skill-mod">
{{skillMod}}
</div>
{{/if}}
<div flex>
{{name}}
@@ -16,7 +18,7 @@
*
{{/if}}
{{#if showPassive}}
({{../passiveSkill skill}})
({{characterCalculate "passiveSkill" ../_id skill}})
{{/if}}
</div>
</div>

View File

@@ -1,13 +1,21 @@
Template.skillRow.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.skill
)
);
},
profIcon: function(){
var prof = Template.parentData(1).proficiency(this.skill);
var charId = Template.parentData()._id;
var prof = Characters.calculate.proficiency(charId, this.skill);
if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album";
return "radio-button-off";
},
failSkill: function(){
var charId = Template.parentData(1)._id;
var charId = Template.parentData()._id;
return Effects.find({
charId: charId,
stat: this.skill,
@@ -16,12 +24,13 @@ Template.skillRow.helpers({
}).count();
},
advantage: function(){
var advantage = Template.parentData(1).advantage(this.skill);
var charId = Template.parentData()._id;
var advantage = Characters.calculate.advantage(charId, this.skill);
if (advantage > 0) return "advantage";
if (advantage < 0) return "disadvantage";
},
conditionalCount: function(){
var charId = Template.parentData(1)._id;
var charId = Template.parentData()._id;
return Effects.find({
charId: charId,
stat: this.skill,

View File

@@ -75,9 +75,9 @@
<div class="left display1 white-text {{color}}"
hero-id="toolbar" {{detailHero stat ../_id}}>
{{#if isSkill}}
{{../skillMod stat}}
{{prefix}}{{skillMod}}
{{else}}
{{prefix}}{{../attributeValue stat}}
{{prefix}}{{characterCalculate "attributeValue" ../_id stat}}
{{/if}}
</div>
<div class="right subhead" flex horizontal layout center>

View File

@@ -67,6 +67,12 @@ Template.stats.events({
},
});
Template.stats.helpers({
Template.statCard.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.stat
)
);
},
});

View File

@@ -1,5 +1,5 @@
<template name="characterList">
<core-toolbar class="blue-grey white-text">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
Characters

View File

@@ -13,7 +13,10 @@ Template.characterSideList.helpers({
{owner: userId},
]
},
{fields: {name: 1}}
{
fields: {name: 1},
sort: {name: 1},
}
);
}
});

View File

@@ -1,11 +1,10 @@
<template name="guide">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>Guide</div>
</core-toolbar>
<div layout vertical center>
<paper-shadow class="wallOfText card" style="padding: 32px; max-width: 800px;">
<h1>Dicecloud Beta</h1>
<p>Welcome to the Dicecloud beta.</p>
<p>The beta is going to start with just the character sheet. You can play D&amp;D without minis and maps, without a pre-written adventure, you can play without a lot of things, but the character sheet is necessary. So I'm starting here and working my way outwards.</p>
<p>Leave any comments, feedback and suggestions on <a href="http://reddit.com/r/dicecloud">this subreddit</a>. If you've never used reddit before, all you need is a username and password to sign up. So it should be pretty accessible.</p>
<p>If you'd like to see a list of known issues and upcoming features, check out the <a href="https://trello.com/b/94M0SCnq/dicecloud-roadmap">DiceCloud Roadmap</a>.</p>
<h2>Character Sheet Philosophy</h2>
<p>Setting up your character on Dicecloud is going to take you a little longer than just filling it in on a paper character sheet would have. The goal of using an online sheet is to make actually playing the game more streamlined, and ultimately more fun. So putting a little extra effort into setting up your character now will pay off over and over again once you're playing.</p>
<p>The idea is to track where each number comes from, and allow you to easily make changes on the fly.</p>

View File

@@ -1,11 +1,9 @@
<template name="home">
<core-toolbar class="blue-grey white-text">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
DiceCloud
Home
</div>
</core-toolbar>
<div class="scroll-y" style="padding: 16px" fit>
{{> guide}}
</div>
{{> intro}}
</template>

View File

@@ -1,25 +1,15 @@
Template.home.helpers({
characterDetails: function(){
var char = Characters.findOne(
this._id,
{fields: {name: 1, gender: 1, alignment: 1, race:1}}
);
char.title = char.name;
char.field = "base";
char.color = "d";
char.class = "characterCard";
return char;
}
selectedTab: function(){
return Session.get("homePage.selectedTab");
},
});
Template.home.events({
"tap .characterCard": function(event, instance){
Router.go("characterSheet", {_id: this._id});
"core-animated-pages-transition-end .tabPages": function(event) {
event.stopPropagation();
},
"tap #addCharacter": function(event, template) {
Characters.insert({owner: Meteor.userId()});
},
"tap #deleteChar": function(event, template){
Characters.remove(this._id);
"tap .homeTabs paper-tab": function(event, instance){
Session.set("homePage.selectedTab",
event.currentTarget.getAttribute("name"));
},
});

View File

@@ -0,0 +1,98 @@
<template name="intro">
<div class="intro">
<div class="section white-text" style="background: #282828">
<div class="display2">
Dice Cloud
</div>
<img style="width:130px; height:130px; background-color: #282828;"
src="/crown-dice-logo-cropped-transparent.png">
<div class="display1">
Unofficial Online Realtime D&amp;D 5e App
</div>
<h2>
Spend less time shuffling paper and more time playing the game
</h2>
{{#unless currentUser}}
<div layout horizontal around-justified wrap>
<paper-button class="red white-text signInButton"
style="margin: 16px;"
raised>
Sign In
</paper-button>
<paper-button class="red white-text signUpButton"
style="margin: 16px;"
raised>
Sign Up
</paper-button>
</div>
{{else}}
<div style="padding-bottom: 0;"></div>
{{/unless}}
</div>
<div class="section" style="background-color: #e9e9e9;">
<div>
<div class="display1">Character Sheet Open Beta</div>
<h2 style="margin-bottom: 16px;">
Check out the example characters
</h2>
<div layout horizontal around-justified wrap>
<paper-shadow class="card characterCard ssArcher clickable"
z="2">
<div class="top subhead green white-text">
<div class="subhead" flex>
Starter Set Archer
</div>
</div>
<div class="bottom text">Lawful Good Human</div>
</paper-shadow>
<paper-shadow class="card characterCard ssWizard clickable"
z="2">
<div class="top subhead deep-purple white-text">
<div class="subhead" flex>
Starter Set Wizard
</div>
</div>
<div class="bottom text">Chaotic Good High Elf</div>
</paper-shadow>
</div>
</div>
</div>
<div class="section white-text" style="background: #282828">
<div class="columns" layout horizontal around-justified wrap>
<div>
<h1>Check out the guide</h1>
<p>
Learn how your class gives you features, those features have effects,
and those effects determine your stats.
<paper-button class="guideButton">View Guide</paper-button>
</p>
</div>
<div>
<h1>
Discuss
</h1>
<p>
On the official subreddit
<paper-button class="redditButton">
<a href="http://www.reddit.com/r/dicecloud/">
/r/dicecloud
</a>
</paper-button>
</p>
</div>
<div>
<h1>
Get involved
</h1>
<p>Shape upcoming features and track bugs on the Dice Cloud Trello board
<paper-button class="trelloButton">
<a href="https://trello.com/b/94M0SCnq/dicecloud-roadmap">
Trello Roadmap
</a>
</paper-button>
</p>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,17 @@
Template.intro.events({
"tap .signInButton": function() {
Router.go("/sign-in");
},
"tap .signUpButton": function() {
Router.go("/sign-up");
},
"tap .ssArcher": function() {
Router.go("/character/yBWwt5XQTTHZiRQxq");
},
"tap .ssWizard": function() {
Router.go("/character/KxHKskm22fS2Xogah");
},
"tap .guideButton": function() {
Router.go("/guide");
},
});

View File

@@ -0,0 +1,33 @@
.intro {
.section {
width: 100%;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding-top: 24px;
padding-bottom: 24px;
& > div, & > h2 {
padding: 32px;
.display1 {
margin-bottom: 16px;
}
}
.columns > div{
max-width: 300px;
padding: 16px;
text-align: center;
paper-button {
color: #FF5252;
}
}
}
paper-button {
min-width: 200px;
a {
color: inherit;
text-decoration: inherit;
}
}
}

View File

@@ -8,6 +8,7 @@
<link rel="import" href="/components/core-icons/editor-icons.html">
<link rel="import" href="/components/core-icons/image-icons.html">
<link rel="import" href="/components/core-icons/social-icons.html">
<link rel="import" href="/components/core-image/core-image.html">
<link rel="import" href="/components/core-item/core-item.html">
<link rel="import" href="/components/core-menu/core-menu.html">
<link rel="import" href="/components/core-scaffold/core-scaffold.html">
@@ -32,11 +33,12 @@
<link rel="import" href="/components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="/components/paper-shadow/paper-shadow.html">
<link rel="import" href="/components/paper-spinner/paper-spinner.html">
<link rel="import" href="/components/paper-slider/paper-slider.html">
<link rel="import" href="/components/paper-tabs/paper-tabs.html">
<link rel="import" href="/components/paper-toast/paper-toast.html">
<link rel="import" href="/components/paper-toggle-button/paper-toggle-button.html">
<!--custom components-->
<link rel="import" href="/custom_components/paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="/custom_components/paper-slider-diff/paper-slider.html">
<link rel="import" href="/custom_components/paper-diff-slider/paper-diff-slider.html">
<link rel="import" href="/custom_components/swipe-detect/swipe-detect.html">

View File

@@ -3,7 +3,8 @@
<core-header-panel drawer navigation flex mode="seamed" class="white">
<div id="accountSummary">
{{#if currentUser}}
<div id="profileLink" style="text-decoration: underline; cursor: pointer;">
<div id="profileLink"
style="text-decoration: underline; cursor: pointer; font-size: 16px;">
{{profileLink}}
</div>
{{else}}
@@ -13,6 +14,7 @@
<div id="navPanel">
<core-item id="homeNav" icon="home" label="Home"></core-item>
{{> characterSideList}}
<core-item id="guide" icon="social:school" label="Guide"></core-item>
<core-item id="feedback" icon="bug-report" label="Send Feedback"></core-item>
<core-item id="changeLog" icon="list" label="Change Log"></core-item>
</div>
@@ -23,7 +25,7 @@
transitions="hero-transition cross-fade"
selected={{globalDetailSelected}}>
<section id="mainContentSection" class={{notSelected}}>
<core-header-panel fit mode="seamed">
<core-header-panel fit mode="standard">
{{> yield}}
</core-header-panel>
<div>

View File

@@ -41,4 +41,8 @@ Template.layout.events({
Router.go("changeLog");
instance.find("core-drawer-panel").closeDrawer();
},
"tap #guide": function(event, instance) {
Router.go("guide");
instance.find("core-drawer-panel").closeDrawer();
},
});

View File

@@ -1,4 +1,5 @@
.bigSpinner {
width: 100px;
height: 100px;
margin-bottom: 16px;
}

View File

@@ -1,9 +1,12 @@
<template name="loading">
<core-toolbar class="blue-grey white-text">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
</core-toolbar>
<div fit layout vertical center center-justified>
<paper-spinner class="bigSpinner" active></paper-spinner>
<div class="subhead">{{randomHint}}</div>
<div class="subhead"
style="margin-left: 16px;
margin-right: 16px;
text-align: center;">{{randomHint}}</div>
</div>
</template>

View File

@@ -1,5 +1,5 @@
<template name="changeLog">
<core-toolbar class="blue-grey white-text">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>
Change Log

View File

@@ -1,10 +1,14 @@
<template name="notFound">
<div layout vertical center center-justified fit>
<h2>The data for the page you requested could not be found.</h2>
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
</core-toolbar>
<div layout vertical center center-justified fit
style="padding: 16px; text-align: center;">
<h2 style="margin-bottom: 12px;">The data for the page you requested could not be found.</h2>
{{#if currentUser}}
<h2>It might not exist, or you might not have permission to view it.</h2>
<h3>It might not exist, or you might not have permission to view it.</h3>
{{else}}
<h2>Perhaps you need to sign in first:</h2>
<h3>Perhaps you need to sign in first:</h3>
{{atForm}}
{{/if}}
</div>

View File

@@ -13,22 +13,22 @@
role="button"
tabindex="0"
icon="delete"
aria-label="Delete Feature"
noink></paper-icon-button>
aria-label="Delete Feature">
</paper-icon-button>
{{/unless}}
{{#unless hideColor}}
{{> colorDropdown}}
{{/unless}}
<paper-icon-button id="doneEditingButton"
icon="done"
aria-label="Delete Feature"
noink></paper-icon-button>
aria-label="Delete Feature">
</paper-icon-button>
{{else}}
{{#if showEdit}}
<paper-icon-button id="editButton"
icon="create"
aria-label="Delete Feature"
noink></paper-icon-button>
aria-label="Delete Feature">
</paper-icon-button>
{{/if}}
{{/if}}
</div>

View File

@@ -9,19 +9,11 @@ Template.baseDialog.onRendered(function(){
Template.baseDialog.helpers({
editing: function(){
return Template.instance().editing.get();
return Template.instance().editing.get() && canEditCharacter(Template.parentData().charId);
},
showEdit: function() {
if (this.hideEdit) return false;
var charId = Template.parentData().charId;
if (charId){
var char = Characters.findOne(charId);
var userId = Meteor.userId();
if (char && userId)
return char.owner === userId ||
_.contains(char.writers, userId);
}
return true;
return canEditCharacter(Template.parentData().charId);
},
});

View File

@@ -1,5 +1,5 @@
<template name="titledAtForm">
<core-toolbar class="blue-grey white-text">
<core-toolbar class="app-grey white-text">
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
<div flex>

View File

@@ -1,11 +1,10 @@
//evaluates a calculation string
evaluate = function(charId, string){
if (!string) return string;
var char = Characters.findOne(charId, {fields: {_id: 1}});
string = string.replace(/\b[a-z]+\b/gi, function(sub){
//fields
if (Schemas.Character.schema(sub)){
return char.fieldValue(sub);
return Characters.calculate.fieldValue(charId, sub);
}
//ability modifiers
var abilityMods = [
@@ -19,7 +18,7 @@ evaluate = function(charId, string){
if (_.contains(abilityMods, sub)){
var slice = sub.slice(0, -3);
try {
return char.abilityMod(slice);
return Characters.calculate.abilityMod(charId, slice);
} catch (e){
return sub;
}
@@ -33,7 +32,7 @@ evaluate = function(charId, string){
}
//character level
if (sub.toUpperCase() === "LEVEL"){
return char.level();
return Characters.calculate.level(charId);
}
return sub;
});
@@ -41,7 +40,6 @@ evaluate = function(charId, string){
var result = math.eval(string);
return result;
} catch (e){
console.log("Failed to evaluate ", string);
return string;
}
};

View File

@@ -1,4 +1,5 @@
preventLoop = function(inputFunction){
var self = this;
if (!_.isFunction(inputFunction)){
throw new Meteor.Error(
"Not a function",
@@ -9,23 +10,26 @@ preventLoop = function(inputFunction){
//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(argument){
var value;
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, argument)) {
if (_.contains(visitedArgs, hash)) {
console.warn("dependency loop detected");
return NaN;
} else {
//push this skill to the list of visited skills
//push this hash to the list of visited hashes
//we can't visit it again unless it returns first
visitedArgs.push(argument);
visitedArgs.push(hash);
}
try {
value = inputFunction.call(this, argument);
result = inputFunction.apply(this, arguments);
} finally{
//this argument returns or fails, pull it from the array
visitedArgs = _.without(visitedArgs, argument);
//this hash returns or fails, pull it from the array
visitedArgs = _.without(visitedArgs, hash);
}
return value;
return result;
};
};

View File

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

View File

@@ -0,0 +1,60 @@
Tracker.memoize = function(func, hasher){
var memoize = function(key) {
var cache = memoize.cache;
var address = "" + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address)) {
cache[address] = new CacheObject(func, address, arguments, cache, this);
}
return cache[address].get();
};
memoize.cache = {};
return memoize;
};
function CacheObject(func, address, args, cache, context){
var self = this;
self.currentValue = null;
self.dep = new Tracker.Dependency();
self.numRun = 0;
//spawn a new autorun that keeps the value up-to-date
Tracker.nonreactive(function() {
Tracker.autorun(function(computation) {
//if this isn't the first run and nobody is listening,
//delete itself from cache and stop the computation
if (!computation.firstRun && !self.dep.hasDependents()){
computation.stop();
delete cache[address];
return;
}
//if we haven't run this before this flush, reset the counter after the flush
if(self.numRun === 0){
Tracker.afterFlush(function(){
self.numRun = 0;
});
}
self.numRun++;
//call the expensive function
//even if we don't use its value, we need to track its dependencies
var newValue = func.apply(context, args);
//prevent dependency loops, the memoized function shouldn't re-run
//more than once per flush
if (self.numRun > 1){
newValue = NaN;
if(_.isNaN(self.currentValue)) return;
}
//if the value changed, store the new value
if (self.currentValue !== newValue){
self.currentValue = newValue;
//tell the dependents that we've changed
self.dep.changed();
}
});
});
}
CacheObject.prototype.get = function() {
//if there is an active computation, track dependents
if (Tracker.active) this.dep.depend();
return this.currentValue;
};

View File

@@ -98,9 +98,11 @@ trackEncumbranceConditions = function(charId, templateInstance) {
});
var character = Characters.findOne(
charId,
{fields: {strength: 1, "settings": 1}}
{fields: {"settings": 1}}
);
var strength = character.attributeValue("strength");
var strength = Characters.calculate.attributeValue(charId, "strength");
var carryMultiplier = Characters.calculate
.attributeValue(charId, "carryMultiplier");
var give = function(condition) {
Meteor.call("giveCondition", charId, condition);
};
@@ -108,11 +110,11 @@ trackEncumbranceConditions = function(charId, templateInstance) {
Meteor.call("removeCondition", charId, condition);
};
//variant encumbrance rules
if (weight > strength * 10 &&
if (weight > strength * 10 * carryMultiplier &&
character.settings.useVariantEncumbrance) {
give("encumbered2");
remove("encumbered");
} else if (weight > strength * 5 &&
} else if (weight > strength * 5 * carryMultiplier &&
character.settings.useVariantEncumbrance){
give("encumbered");
remove("encumbered2");
@@ -121,11 +123,11 @@ trackEncumbranceConditions = function(charId, templateInstance) {
remove("encumbered2");
}
//normal encumbrance rules
if (weight > strength * 30 &&
if (weight > strength * 30 * carryMultiplier &&
character.settings.useStandardEncumbrance){
give("encumbered4");
remove("encumbered3");
} else if (weight > strength * 15 &&
} else if (weight > strength * 15 * carryMultiplier &&
character.settings.useStandardEncumbrance) {
give("encumbered3");
remove("encumbered4");

View File

@@ -135,7 +135,7 @@ ChangeLogs.insert({
});
ChangeLogs.insert({
version: "0.5.2",
version: "0.5.3",
changes: [
"Prevented a harmless error caused by effects which have no stat set",
"Added the ability to hide the spells tab",
@@ -144,3 +144,119 @@ ChangeLogs.insert({
"Overhauled how effects' stats are chosen",
],
});
ChangeLogs.insert({
version: "0.5.4",
changes: [
"Fixed rounding error on net worth calculation",
],
});
ChangeLogs.insert({
version: "0.5.5",
changes: [
"Fixed reports from google users not correctly storing the reply-to email address",
],
});
ChangeLogs.insert({
version: "0.5.6",
changes: [
"Changed front page",
"Moved to a darker style",
"Added support for letting characters be viewed by \"anyone with the link\"",
],
});
ChangeLogs.insert({
version: "0.5.7",
changes: [
"Added proficiencies to backgrounds",
"Added attacks to spells",
"Added a move item dialog to mobile devices, but it can't be accessed yet, because long-presses are broken",
],
});
ChangeLogs.insert({
version: "0.6.0",
changes: [
"Big performance improvements: loading characters that you've viewed recently and changing effects on characters should be much faster",
"Spell dialogs no longer show their casting time, range, etc. if those fields aren't filled in",
"The settings dialog now has a done button so it can be closed on small devices",
"Container dialogs now have the weight summaries rounded down properly",
],
});
ChangeLogs.insert({
version: "0.6.1",
changes: [
"Experience dialogs should update their edit-mode inputs properly now",
],
});
ChangeLogs.insert({
version: "0.6.2",
changes: [
"Fixed a regression which broke min and max effects",
],
});
ChangeLogs.insert({
version: "0.6.3",
changes: [
"Fixed a regression that stopped skills and attributes from rounding down correctly",
"Made dependency loops return NaN immediately, rather than looping indefinitely until a page refresh. Adding an effect that adds \"strength\" to Strength, won't cause Strength to constantly increase or freeze the browser, Strength just becomes NaN",
],
});
ChangeLogs.insert({
version: "0.6.4",
changes: [
"Hit dice now has a \"+\" between the dice and the constitution modifier",
"Items with multiple attacks should have the damage type editable on all attacks now",
"Spell attacks should now correctly inherit changes to their spell's name",
"Character lists should now be ordered alphabetically",
"Skills should have correct min and max effects applied again",
],
});
ChangeLogs.insert({
version: "0.6.5",
changes: [
"Net worth now takes container values into account",
"Added support for character pictures",
"Disabled edit buttons for read-only viewers",
],
});
ChangeLogs.insert({
version: "0.6.6",
changes: [
"Text fields now accept github-flavor markdown formatting",
],
});
ChangeLogs.insert({
version: "0.6.7",
changes: [
"Fixed effect values not being visible on small screens",
"Added basic analytics",
],
});
ChangeLogs.insert({
version: "0.6.8",
changes: [
"Fixed share dialog not finding user names",
"Fixed temporary hitpoint sliders allowing negative values",
"Fixed proficiencies not being disabled with their features",
],
});
ChangeLogs.insert({
version: "0.7.0",
changes: [
"Added carry capacity multiplier as a stat with a default value of 1",
"Improved loading times by vulcanizing polymer imports",
],
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -8,55 +8,55 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
-->
<!--
`paper-slider` allows user to select a value from a range of values by
`paper-diff-slider` allows user to select a value from a range of values by
moving the slider thumb. The interactive nature of the slider makes it a
great choice for settings that reflect intensity levels, such as volume,
brightness, or color saturation.
Example:
<paper-slider></paper-slider>
<paper-diff-slider></paper-diff-slider>
Use `min` and `max` to specify the slider range. Default is 0 to 100.
Example:
<paper-slider min="10" max="200" value="110"></paper-slider>
<paper-diff-slider min="10" max="200" value="110"></paper-diff-slider>
Styling slider:
To change the slider progress bar color:
paper-slider::shadow #sliderBar::shadow #activeProgress {
paper-diff-slider::shadow #sliderBar::shadow #activeProgress {
background-color: #0f9d58;
}
To change the slider knob color:
paper-slider::shadow #sliderKnobInner {
paper-diff-slider::shadow #sliderKnobInner {
background-color: #0f9d58;
}
To change the slider pin color:
paper-slider::shadow #sliderKnobInner::before {
paper-diff-slider::shadow #sliderKnobInner::before {
background-color: #0f9d58;
}
To change the slider pin's font color:
paper-slider::shadow #sliderKnob > #sliderKnobInner::after {
paper-diff-slider::shadow #sliderKnob > #sliderKnobInner::after {
color: #0f9d58
}
To change the slider secondary progress bar color:
paper-slider::shadow #sliderBar::shadow #secondaryProgress {
paper-diff-slider::shadow #sliderBar::shadow #secondaryProgress {
background-color: #0f9d58;
}
@group Paper Elements
@element paper-slider
@element paper-diff-slider
@extends core-range
@homepage github.io
-->
@@ -65,10 +65,10 @@ To change the slider secondary progress bar color:
<link rel="import" href="../../components/paper-progress/paper-progress.html">
<link rel="import" href="../../components/paper-input/paper-input.html">
<polymer-element name="paper-slider" extends="core-range" attributes="snaps pin disabled secondaryProgress editable immediateValue" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100">
<polymer-element name="paper-diff-slider" extends="core-range" attributes="snaps pin disabled secondaryProgress editable immediateValue" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100">
<template>
<link rel="stylesheet" href="paper-slider.css">
<link rel="stylesheet" href="paper-diff-slider.css">
<template if="{{!disabled}}">
<core-a11y-keys target="{{}}" keys="left down pagedown home" on-keys-pressed="{{decrementKey}}"></core-a11y-keys>
@@ -109,7 +109,7 @@ To change the slider secondary progress bar color:
</template>
<script>
Polymer('paper-slider', {
Polymer('paper-diff-slider', {
/**
* Fired when the slider's value changes.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -108,3 +108,45 @@ Migrations.add({
);
},
});
Migrations.add({
version: 4,
name: "Adds an effect to give characters a base carry capacity",
up: function() {
//update characters
Characters.find({}).forEach(function(char){
Characters.update(char._id, {
$set: {
carryMultiplier: {
adjustment: 0,
reset: "longRest",
}
}
});
var effect = Effects.findOne({
charId: char._id, name: "Natural Carrying Capacity"
});
if (effect) return;
Effects.insert({
charId: char._id,
name: "Natural Carrying Capacity",
stat: "carryMultiplier",
operation: "base",
value: "1",
parent: {
id: char._id,
collection: "Characters",
group: "Inate",
},
});
effect = Effects.findOne({
charId: char._id, name: "Natural Carrying Capacity"
});
if (!effect) throw "Carry capacity effect should be set by now."
});
},
down: function(){
return;
},
});

Some files were not shown because too many files have changed in this diff Show More