From 52bef57637c57afb1ce118b086bc33cb3870affe Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 22 May 2015 14:04:09 +0200 Subject: [PATCH] Added encumbrance effects, conditions and encumbrance buffs --- rpg-docs/Model/Character/Characters.js | 42 ++-- rpg-docs/client/style/cards.scss | 5 + .../buffs/buffDialog/buffDialog.html | 15 ++ .../character/buffs/buffDialog/buffDialog.js | 5 + .../characterSettings/characterSettings.html | 12 +- .../characterSettings/characterSettings.js | 18 +- .../views/character/characterSheet.html | 3 + .../client/views/character/characterSheet.js | 14 +- .../carryCapacityBar/carryCapacityBar.html | 8 + .../carryCapacityBar/carryCapacityBar.js | 31 ++- .../views/character/inventory/inventory.html | 23 +- .../views/character/inventory/inventory.js | 21 ++ rpg-docs/lib/methods/conditions.js | 196 +++++++++++------- rpg-docs/server/migrations/migrations.js | 18 ++ 14 files changed, 312 insertions(+), 99 deletions(-) create mode 100644 rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html create mode 100644 rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index bc35df51..e643b3b5 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -168,26 +168,26 @@ Schemas.Character = new SimpleSchema({ defaultValue: "q", }, //TODO add per-character settings - "settings.experiencesInc": {type: Number, defaultValue: 20}, //how many experiences to load at a time in XP table + //how many experiences to load at a time in XP table + "settings.experiencesInc": {type: Number, defaultValue: 20}, + //slowed down by carrying too much? + "settings.useVariantEncumbrance": {type: Boolean, defaultValue: false}, + "settings.useStandardEncumbrance": {type: Boolean, defaultValue: true}, }); Characters.attachSchema(Schemas.Character); var attributeBase = function(charId, statName){ check(statName, String); - var effects = Effects.find( - {charId: charId, stat: statName, enabled: true} - ).fetch(); - effects = _.groupBy(effects, "operation"); - var isMultiplier = _.contains(DAMAGE_MULTIPLIERS, statName); - var value = 0; - //if it's a damage multiplier, we treat it specially - if (isMultiplier){ + 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.mul, function(effect){ + _.each(effects, function(effect){ var val = evaluateEffect(charId, effect); if (val === 0.5){ //resistance resistCount += 1; @@ -212,8 +212,12 @@ var attributeBase = function(charId, statName){ } } + var value = 0; + //start with the highest base value - _.each(effects.base, function(effect){ + Effects.find( + {charId: charId, stat: statName, enabled: true, operation: "base"} + ).forEach(function(effect){ var efv = evaluateEffect(charId, effect); if (efv > value){ value = efv; @@ -221,23 +225,31 @@ var attributeBase = function(charId, statName){ }); //add all the add values - _.each(effects.add, function(effect){ + Effects.find( + {charId: charId, stat: statName, enabled: true, operation: "add"} + ).forEach(function(effect){ value += evaluateEffect(charId, effect); }); //multiply all the mul values - _.each(effects.mul, function(effect){ + Effects.find( + {charId: charId, stat: statName, enabled: true, operation: "mul"} + ).forEach(function(effect){ value *= evaluateEffect(charId, effect); }); //ensure value is >= all mins - _.each(effects.min, function(effect){ + Effects.find( + {charId: charId, stat: statName, enabled: true, operation: "min"} + ).forEach(function(effect){ var min = evaluateEffect(charId, effect); value = value > min ? value : min; }); //ensure value is <= all maxes - _.each(effects.max, function(effect){ + Effects.find( + {charId: charId, stat: statName, enabled: true, operation: "max"} + ).forEach(function(effect){ var max = evaluateEffect(charId, effect); value = value < max ? value : max; }); diff --git a/rpg-docs/client/style/cards.scss b/rpg-docs/client/style/cards.scss index 533bfc5f..940724df 100644 --- a/rpg-docs/client/style/cards.scss +++ b/rpg-docs/client/style/cards.scss @@ -89,3 +89,8 @@ $thinColumnWidth: 240px; border-radius: 0 2px 2px 0; } } + +/* undo pointer cursor on detail box heading */ +#globalDetail .card .top { + cursor: auto; +} diff --git a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html new file mode 100644 index 00000000..165dd377 --- /dev/null +++ b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html @@ -0,0 +1,15 @@ + + + diff --git a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js new file mode 100644 index 00000000..be2e6d18 --- /dev/null +++ b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js @@ -0,0 +1,5 @@ +Template.buffDialog.helpers({ + buff: function(){ + return Buffs.findOne(this.buffId); + }, +}); diff --git a/rpg-docs/client/views/character/characterSettings/characterSettings.html b/rpg-docs/client/views/character/characterSettings/characterSettings.html index 56f713b5..745f37bc 100644 --- a/rpg-docs/client/views/character/characterSettings/characterSettings.html +++ b/rpg-docs/client/views/character/characterSettings/characterSettings.html @@ -1,3 +1,13 @@ diff --git a/rpg-docs/client/views/character/characterSettings/characterSettings.js b/rpg-docs/client/views/character/characterSettings/characterSettings.js index 26a60c4f..53777d5e 100644 --- a/rpg-docs/client/views/character/characterSettings/characterSettings.js +++ b/rpg-docs/client/views/character/characterSettings/characterSettings.js @@ -1,3 +1,17 @@ -Template.characterSettings.events({ - +Template.characterSettings.helpers({ + character: function() { + return Characters.findOne(this._id, {fields: {settings: 1}}); + } +}); + +Template.characterSettings.events({ + "change #variantEncumbrance": function(event, instance){ + var value = instance.find("#variantEncumbrance").checked; + if (this.settings.useVariantEncumbrance !== value){ + Characters.update( + this._id, + {$set: {"settings.useVariantEncumbrance": value}} + ); + } + }, }); diff --git a/rpg-docs/client/views/character/characterSheet.html b/rpg-docs/client/views/character/characterSheet.html index 9c4867e3..98b3b6a4 100644 --- a/rpg-docs/client/views/character/characterSheet.html +++ b/rpg-docs/client/views/character/characterSheet.html @@ -19,6 +19,9 @@ Share + + Settings + diff --git a/rpg-docs/client/views/character/characterSheet.js b/rpg-docs/client/views/character/characterSheet.js index a0192af7..76b496dd 100644 --- a/rpg-docs/client/views/character/characterSheet.js +++ b/rpg-docs/client/views/character/characterSheet.js @@ -1,6 +1,9 @@ -Template.characterSheet.created = function(){ +Template.characterSheet.onCreated(function() { + //default to the first tab Session.setDefault(this.data._id + ".selectedTab", "stats"); -}; + //watch this character and make sure their encumbrance is updated + trackEncumbranceConditions(this.data._id, this); +}); var setTab = function(charId, tab){ return Session.set(charId + ".selectedTab", tab); @@ -40,4 +43,11 @@ Template.characterSheet.events({ template: "shareDialog", }); }, + "tap #characterSettings": function(event, instance){ + GlobalUI.showDialog({ + heading: this.name + " Settings", + data: this, + template: "characterSettings", + }); + }, }); diff --git a/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.html b/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.html index 39f05f01..6e584305 100644 --- a/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.html +++ b/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.html @@ -11,4 +11,12 @@ style="width: 66.666%;"> + {{#if overCarriedPercent}} +
+
+
+
+ {{/if}} diff --git a/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.js b/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.js index 6e95ebbe..bc8a42f9 100644 --- a/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.js +++ b/rpg-docs/client/views/character/inventory/carryCapacityBar/carryCapacityBar.js @@ -18,13 +18,40 @@ var getFractionCarried = function(char) { return weight / capacity; }; +Template.carryCapacityBar.onCreated(function() { + var self = this; + self.carriedFraction = new ReactiveVar(0); + self.autorun(function() { + self.carriedFraction.set(getFractionCarried(self.data)); + }); +}); + Template.carryCapacityBar.helpers({ carriedPercent: function() { - var percent = 100 * getFractionCarried(this); + var percent = 100 * Template.instance().carriedFraction.get(); return percent > 100 ? 100 : percent; }, + overCarriedPercent: function() { + var percent = 100 * Template.instance().carriedFraction.get(); + var overPercent = percent - 100; + if (overPercent < 0) return 0; + if (overPercent > 100) return 100; + return overPercent; + }, carriedColor: function() { - var frac = getFractionCarried(this); + var frac = Template.instance().carriedFraction.get(); + if (frac < 1 / 3){ + return "#2196F3"; + } else if (frac < 2 / 3){ + return "#CDDC39"; + } else if (frac < 1) { + return "#FFC107"; + } else { + return "#F44336"; + } + }, + overCarriedColor: function() { + var frac = Template.instance().carriedFraction.get(); if (frac < 1 / 3){ return "#2196F3"; } else if (frac < 2 / 3){ diff --git a/rpg-docs/client/views/character/inventory/inventory.html b/rpg-docs/client/views/character/inventory/inventory.html index f00f8ee2..fcab2eb0 100644 --- a/rpg-docs/client/views/character/inventory/inventory.html +++ b/rpg-docs/client/views/character/inventory/inventory.html @@ -14,9 +14,9 @@ - -
@@ -29,6 +29,25 @@
{{> carryCapacityBar}}
+ {{#if encumberedBuffs.count}} +
+ {{#each encumberedBuffs}} +
+
+
+ + + {{name}} +
+
+
+ {{/each}} +
+ {{/if}} diff --git a/rpg-docs/client/views/character/inventory/inventory.js b/rpg-docs/client/views/character/inventory/inventory.js index 3bf21f30..72b1fb19 100644 --- a/rpg-docs/client/views/character/inventory/inventory.js +++ b/rpg-docs/client/views/character/inventory/inventory.js @@ -61,6 +61,18 @@ Template.inventory.helpers({ }); return weight; }, + encumberedBuffs: function(){ + return Buffs.find({ + charId: this._id, + type: "inate", + name: {$in: [ + "Encumbered", + "Heavily encumbered", + "Over encumbered", + "Can't move load", + ]}, + }); + }, equipmentValue: function(){ var value = 0; Items.find( @@ -144,6 +156,15 @@ Template.inventory.events({ heroId: charId + "weightCarried", }); }, + "tap .buff": function(event){ + var buffId = this._id; + var charId = Template.parentData()._id; + GlobalUI.setDetail({ + template: "buffDialog", + data: {buffId: buffId, charId: charId}, + heroId: buffId, + }); + }, "tap .inventoryItem": function(event){ var itemId = this._id; var charId = Template.parentData()._id; diff --git a/rpg-docs/lib/methods/conditions.js b/rpg-docs/lib/methods/conditions.js index 20f4806d..e5a83801 100644 --- a/rpg-docs/lib/methods/conditions.js +++ b/rpg-docs/lib/methods/conditions.js @@ -1,22 +1,30 @@ +var checkWritePermission = function(charId) { + if (!Meteor.call("canWriteCharacter", charId)){ + throw new Meteor.Error( + "Access denied", + "You do not have permission to edit the assets of this character" + ); + } +}; + +var getCondition = function(conditionName) { + //get condition from constant + var condition = CONDITIONS[conditionName]; + //check that condition exists + if (!condition) { + throw new Meteor.Error( + "Invalid condition", + conditionName + " is not a known condition" + ); + } + return condition; +}; + Meteor.methods({ giveCondition: function(charId, conditionName) { - //check permission - if (!Meteor.call("canWriteCharacter", charId)){ - throw new Meteor.Error( - "Access denied", - "You do not have permission to edit the assets of this character" - ); - } - //get condition from constant - var condition = CONDITIONS[conditionName]; - //check that condition exists - if (!condition) { - throw new Meteor.Error( - "Invalid condition", - conditionName + " is not a known condition" - ); - } - //extend the buff + checkWritePermission(charId); + var condition = getCondition(conditionName); + //create the buff var buff = _.extend( {charId: charId, type: "inate"}, condition.buff ); @@ -26,30 +34,108 @@ Meteor.methods({ if (existingBuffs) return; //remove exclusive conditions _.each(condition.exclusiveConditions, function(exCond) { - Buffs.remove(exCond); + Meteor.call("removeCondition", charId, exCond); }); //insert the buff var buffId = Buffs.insert(buff); //extend and insert each effect _.each(condition.effects, function(effect) { - effect = _.extend( - effect, { - charId: charId, - parent: { - id: buffId, - collection: "Buffs", - }, + var newEffect = { + stat: effect.stat, + operation: effect.operation, + value: effect.value, + charId: charId, + parent: { + id: buffId, + collection: "Buffs", + }, + enabled: true, + }; + //we know these effects are right, + //skip after hooks, skip validation + Effects.direct.insert( + newEffect, + { + validate: false, + filter: false, + autoConvert: false, + removeEmptyStrings: false, } ); - Effects.insert(effect); }); //recurse for subConditions _.each(condition.subConditions, function(subCondition) { Meteor.call("giveCondition", charId, subCondition); }); - } + }, + removeCondition: function(charId, conditionName) { + checkWritePermission(charId); + var condition = getCondition(conditionName); + //remove the buff + var buff = _.extend( + {charId: charId, type: "inate"}, condition.buff + ); + Buffs.remove(buff); + //dont remove the effects, they get removed automatically through parenting + }, }); +trackEncumbranceConditions = function(charId, templateInstance) { + templateInstance.autorun(function() { + //get weight + var weight = 0; + Containers.find( + {charId: charId, isCarried: true}, + {fields: {weight: 1}} + ).forEach(function(container){ + weight += container.totalWeight(); + }); + Items.find( + {charId: charId, "parent.id": charId}, + {fields: {weight : 1, quantity: 1}} + ).forEach(function(item){ + weight += item.totalWeight(); + }); + var character = Characters.findOne( + charId, + {fields: {strength: 1, "settings": 1}} + ); + var strength = character.attributeValue("strength"); + var give = function(condition) { + Meteor.call("giveCondition", charId, condition); + }; + var remove = function(condition) { + Meteor.call("removeCondition", charId, condition); + }; + //variant encumbrance rules + if (weight > strength * 10 && + character.settings.useVariantEncumbrance) { + give("encumbered2"); + remove("encumbered"); + } else if (weight > strength * 5 && + character.settings.useVariantEncumbrance){ + give("encumbered"); + remove("encumbered2"); + } else { + remove("encumbered"); + remove("encumbered2"); + } + //normal encumbrance rules + if (weight > strength * 30 && + character.settings.useStandardEncumbrance){ + give("encumbered4"); + remove("encumbered3"); + } else if (weight > strength * 15 && + character.settings.useStandardEncumbrance) { + give("encumbered3"); + remove("encumbered4"); + } else { + remove("encumbered3"); + remove("encumbered4"); + } + }); +}; + CONDITIONS = { //Conditions blind: { @@ -315,25 +401,16 @@ CONDITIONS = { encumbered: { buff: { name: "Encumbered", - description: "Encumbered characters move 10 feet slower", + description: "Encumbered characters move 10 feet slower.", }, effects: [ - { - stat: "speed", - operation: "add", - value: -10, - } - ], - exclusiveConditions: [ - "heavilyEncumbered", - "overEncumbered", - "cantLift", + {stat: "speed", operation: "add", value: -10} ], }, - heavilyEncumbered: { + encumbered2: { buff: { name: "Heavily encumbered", - description: "Heavily encumbered characters move 20 feet slower and have disadvantage on ability checks, attack rolls, and saving thows that use Strength, Dexterity, or Constitution", + description: "Heavily encumbered characters move 20 feet slower and have disadvantage on ability checks, attack rolls, and saving thows that use Strength, Dexterity, or Constitution.", }, effects: [ {stat: "speed", operation: "add", value: -20}, @@ -346,54 +423,23 @@ CONDITIONS = { {stat: "stealth", operation: "disadvantage", value: 1}, {stat: "initiative", operation: "disadvantage", value: 1}, ], - exclusiveConditions: [ - "encumbered", - "overEncumbered", - "cantLift", - ], }, - overEncumbered: { + encumbered3: { buff: { name: "Over encumbered", - description: "Characters that can only just lift, push or drag their current load can only move at 5 feet and have disadvantage on ability checks, attack rolls, and saving thows that use Strength, Dexterity, or Constitution", + description: "Characters that can only just lift, push or drag their current load move at 5 feet.", }, effects: [ {stat: "speed", operation: "max", value: 5}, - {stat: "strengthSave", operation: "disadvantage", value: 1}, - {stat: "dexteritySave", operation: "disadvantage", value: 1}, - {stat: "constitutionSave", operation: "disadvantage", value: 1}, - {stat: "athletics", operation: "disadvantage", value: 1}, - {stat: "acrobatics", operation: "disadvantage", value: 1}, - {stat: "sleightOfHand", operation: "disadvantage", value: 1}, - {stat: "stealth", operation: "disadvantage", value: 1}, - {stat: "initiative", operation: "disadvantage", value: 1}, - ], - exclusiveConditions: [ - "encumbered", - "heavilyEncumbered", - "cantLift", ], }, - cantLift: { + encumbered4: { buff: { name: "Can't move load", - description: "This character cannot lift, push, or drag more than {30 * strength} pounds. Characters attempting to carry more than what they can lift, push, or drag can't move and have disadvantage on ability checks, attack rolls, and saving thows that use Strength, Dexterity, or Constitution", + description: "Characters attempting to carry more than what they can lift, push, or drag can't move.", }, effects: [ {stat: "speed", operation: "max", value: 0}, - {stat: "strengthSave", operation: "disadvantage", value: 1}, - {stat: "dexteritySave", operation: "disadvantage", value: 1}, - {stat: "constitutionSave", operation: "disadvantage", value: 1}, - {stat: "athletics", operation: "disadvantage", value: 1}, - {stat: "acrobatics", operation: "disadvantage", value: 1}, - {stat: "sleightOfHand", operation: "disadvantage", value: 1}, - {stat: "stealth", operation: "disadvantage", value: 1}, - {stat: "initiative", operation: "disadvantage", value: 1}, - ], - exclusiveConditions: [ - "encumbered", - "heavilyEncumbered", - "overEncumbered", ], }, }; diff --git a/rpg-docs/server/migrations/migrations.js b/rpg-docs/server/migrations/migrations.js index da366157..9310c8e2 100644 --- a/rpg-docs/server/migrations/migrations.js +++ b/rpg-docs/server/migrations/migrations.js @@ -90,3 +90,21 @@ Migrations.add({ ); }, }); + +Migrations.add({ + version: 3, + name: "Converts attacks from damage dice and damage bonus to a string with curly bracket calculations, adds settings.showIncrement to items", + up: function() { + //update characters + Characters.update( + {"settings.useVariantEncumbrance": undefined}, + {$set: {"settings.useVariantEncumbrance" : false}}, + {validate: false, multi: true} + ); + Characters.update( + {"settings.useStandardEncumbrance": undefined}, + {$set: {"settings.useStandardEncumbrance" : true}}, + {validate: false, multi: true} + ); + }, +});