Added encumbrance effects, conditions and encumbrance buffs

This commit is contained in:
Stefan Zermatten
2015-05-22 14:04:09 +02:00
parent 29e9f8c8dc
commit 52bef57637
14 changed files with 312 additions and 99 deletions

View File

@@ -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;
});

View File

@@ -89,3 +89,8 @@ $thinColumnWidth: 240px;
border-radius: 0 2px 2px 0;
}
}
/* undo pointer cursor on detail box heading */
#globalDetail .card .top {
cursor: auto;
}

View File

@@ -0,0 +1,15 @@
<template name="buffDialog">
{{#with buff}}
{{#baseDialog title=name class=colorClass hideEdit=true}}
{{> buffDetails}}
{{/baseDialog}}
{{/with}}
</template>
<template name="buffDetails">
{{#if description}}
<div class="pre-wrap">{{evaluateString charId description}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
</template>

View File

@@ -0,0 +1,5 @@
Template.buffDialog.helpers({
buff: function(){
return Buffs.findOne(this.buffId);
},
});

View File

@@ -1,3 +1,13 @@
<template name="characterSettings">
{{#with character}}
<div>
<div layout horizontal>
<div>Use variant encumbrance </div>
<paper-toggle-button id="variantEncumbrance"
checked={{settings.useVariantEncumbrance}}
touch-action="pan-y">
</paper-toggle-button>
</div>
</div>
{{/with}}
</template>

View File

@@ -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}}
);
}
},
});

View File

@@ -19,6 +19,9 @@
<paper-item id="shareCharacter">
<core-icon icon="social:share"></core-icon>Share
</paper-item>
<paper-item id="characterSettings">
<core-icon icon="settings"></core-icon>Settings
</paper-item>
</core-menu>
</paper-dropdown>
</paper-menu-button>

View File

@@ -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",
});
},
});

View File

@@ -11,4 +11,12 @@
style="width: 66.666%;">
</div>
</div>
{{#if overCarriedPercent}}
<div class="carryCapacityBar">
<div class="carriedWeightBar"
style="width: {{overCarriedPercent}}%;
background-color: {{overCarriedColor}}">
</div>
</div>
{{/if}}
</template>

View File

@@ -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){

View File

@@ -14,9 +14,9 @@
</div>
</paper-shadow>
<!--Weight Carried-->
<paper-shadow class="card weightCarried"
<paper-shadow class="card"
hero-id="main" {{detailHero "weightCarried" _id}}>
<div class="top green white-text"
<div class="top green white-text weightCarried"
hero-id="toolbar" {{detailHero "weightCarried" _id}}
layout horizontal center>
<div class="subhead" flex>
@@ -29,6 +29,25 @@
<div class="bottom green" style="padding: 0;">
{{> carryCapacityBar}}
</div>
{{#if encumberedBuffs.count}}
<div class="bottom list">
{{#each encumberedBuffs}}
<div class="item-slot">
<div class="item buff"
hero-id="main" {{detailHero}}
layout horizontal center
draggable="true">
<div flex>
<core-icon icon="work"
style="margin-right: 16px">
</core-icon>
{{name}}
</div>
</div>
</div>
{{/each}}
</div>
{{/if}}
</paper-shadow>
<!--Equipment-->
<paper-shadow class="card equipmentContainer">

View File

@@ -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;

View File

@@ -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",
],
},
};

View File

@@ -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}
);
},
});