Implemented Features and Items granting effects, actions, attacks and spells

This commit is contained in:
Thaum
2015-01-06 12:25:58 +00:00
parent c55f2c51ab
commit 2d70119ee0
48 changed files with 625 additions and 442 deletions

View File

@@ -1,13 +1,11 @@
TODO
====
Strip the core interaction out of swipe-pages to create an element that just listens to swipe events
wishlist:
* swipes are emitted as an event if possible
* swiping should translate the element
* it should bounce back to its resting position when released
* Get Polymer installed using bower.
* Install Vulcanize package listed below
* Copy the differential polymer demo to get polymer implemented nicely
* Update Meteor
* Install and use LESS
Packages used
=============
@@ -48,8 +46,13 @@ Packages used
* aldeed:autoform
* Automatically generates bootstrap forms for collection2 Schemas.
* [github](https://github.com/aldeed/meteor-autoform)
* differential:vulcanize
* Bakes all the polymer imports into one file
* [github](https://github.com/Differential/meteor-vulcanize)
************
To Speed up builds when you know you wont need online package checking use
METEOR_OFFLINE_CATALOG=1
Resources
=========
[differential's polymer demo](https://github.com/Differential/polymer-demo)

1
rpg-docs/.bowerrc Normal file
View File

@@ -0,0 +1 @@
{"directory":"public/components/"}

3
rpg-docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.meteor/local
.meteor/meteorite
public/components

View File

@@ -1 +1 @@
METEOR@1.0.1
METEOR@1.0.2.1

View File

@@ -1,77 +1,74 @@
accounts-base@1.1.2
accounts-password@1.0.4
accounts-ui-unstyled@1.1.4
accounts-ui@1.1.3
aldeed:autoform@4.1.0
aldeed:collection2@2.2.0
accounts-base@1.1.3
accounts-password@1.0.5
accounts-ui@1.1.4
accounts-ui-unstyled@1.1.5
aldeed:autoform@4.2.2
aldeed:collection2@2.3.0
aldeed:simple-schema@1.1.0
application-configuration@1.0.3
autoupdate@1.1.3
base64@1.0.1
binary-heap@1.0.1
blaze-tools@1.0.1
blaze@2.0.3
boilerplate-generator@1.0.1
callback-hook@1.0.1
check@1.0.2
ctl-helper@1.0.4
ctl@1.0.2
application-configuration@1.0.4
autoupdate@1.1.4
base64@1.0.2
binary-heap@1.0.2
blaze@2.0.4
blaze-tools@1.0.2
boilerplate-generator@1.0.2
callback-hook@1.0.2
check@1.0.3
cw4gn3r:jquery-event-drag@2.2.0
dburles:collection-helpers@1.0.1
ddp@1.0.12
deps@1.0.5
ejson@1.0.4
email@1.0.4
fastclick@1.0.1
follower-livedata@1.0.2
geojson-utils@1.0.1
html-tools@1.0.2
htmljs@1.0.2
http@1.0.8
id-map@1.0.1
insecure@1.0.1
iron:controller@1.0.3
iron:core@1.0.3
iron:dynamic-template@1.0.3
iron:layout@1.0.3
iron:location@1.0.3
iron:middleware-stack@1.0.3
iron:router@1.0.3
iron:url@1.0.3
jquery@1.0.1
json@1.0.1
launch-screen@1.0.0
less@1.0.11
livedata@1.0.11
localstorage@1.0.1
logging@1.0.5
meteor-platform@1.2.0
meteor@1.1.3
minifiers@1.1.2
minimongo@1.0.5
mobile-status-bar@1.0.1
mongo-livedata@1.0.6
mongo@1.0.9
mrt:moment@2.6.0
dburles:collection-helpers@1.0.2
ddp@1.0.13
deps@1.0.6
ejson@1.0.5
email@1.0.5
fastclick@1.0.2
follower-livedata@1.0.3
geojson-utils@1.0.2
html-tools@1.0.3
htmljs@1.0.3
http@1.0.9
id-map@1.0.2
insecure@1.0.2
iron:controller@1.0.6
iron:core@1.0.6
iron:dynamic-template@1.0.6
iron:layout@1.0.6
iron:location@1.0.6
iron:middleware-stack@1.0.6
iron:router@1.0.6
iron:url@1.0.6
jquery@1.0.2
json@1.0.2
launch-screen@1.0.1
less@1.0.12
livedata@1.0.12
localstorage@1.0.2
logging@1.0.6
meteor@1.1.4
meteor-platform@1.2.1
minifiers@1.1.3
minimongo@1.0.6
mobile-status-bar@1.0.2
momentjs:moment@2.8.4
mongo@1.0.11
npm-bcrypt@0.7.7
observe-sequence@1.0.3
ordered-dict@1.0.1
random@1.0.1
reactive-dict@1.0.4
reactive-var@1.0.3
reload@1.1.1
retry@1.0.1
routepolicy@1.0.2
service-configuration@1.0.2
session@1.0.4
sha@1.0.1
spacebars-compiler@1.0.3
spacebars@1.0.3
srp@1.0.1
templating@1.0.9
tracker@1.0.3
ui@1.0.4
underscore@1.0.1
url@1.0.2
webapp-hashing@1.0.1
webapp@1.1.4
observe-sequence@1.0.4
ordered-dict@1.0.2
random@1.0.2
reactive-dict@1.0.5
reactive-var@1.0.4
reload@1.1.2
retry@1.0.2
routepolicy@1.0.3
service-configuration@1.0.3
session@1.0.5
sha@1.0.2
spacebars@1.0.4
spacebars-compiler@1.0.4
srp@1.0.2
templating@1.0.10
tracker@1.0.4
ui@1.0.5
underscore@1.0.2
url@1.0.3
webapp@1.1.5
webapp-hashing@1.0.2

View File

@@ -37,7 +37,7 @@ Schemas.Character = new SimpleSchema({
"proficiencyBonus.effects": {
type: [Schemas.Effect],
defaultValue: [
{name: "Proficiency bonus by level", calculation: "floor(level / 4.1) + 2", operation: "add", type: "inate"}
{name: "Proficiency bonus by level", calculation: "floor(level / 4 + 1.75)", operation: "add", type: "inate"}
]
},
speed: {type: Schemas.Attribute},
@@ -68,6 +68,8 @@ Schemas.Character = new SimpleSchema({
superiorityDice: {type: Schemas.Attribute},
expertiseDice: {type: Schemas.Attribute},
//specific features
rageDamage: {type: Schemas.Attribute},
//hit dice
d6HitDice: {type: Schemas.Attribute},
@@ -174,52 +176,24 @@ Schemas.Character = new SimpleSchema({
strengthAttack: {type: Schemas.Skill},
"strengthAttack.ability": {type: String,defaultValue: "strength"},
"strengthAttack.effects": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
dexterityAttack: {type: Schemas.Skill},
"dexterityAttack.ability": { type: String, defaultValue: "dexterity" },
"dexterityAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
constitutionAttack: {type: Schemas.Skill},
"constitutionAttack.ability":{ type: String, defaultValue: "constitution" },
"constitutionAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
intelligenceAttack: {type: Schemas.Skill},
"intelligenceAttack.ability":{ type: String, defaultValue: "intelligence" },
"intelligenceAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
wisdomAttack: {type: Schemas.Skill},
"wisdomAttack.ability": { type: String, defaultValue: "wisdom" },
"wisdomAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
charismaAttack: {type: Schemas.Skill},
"charismaAttack.ability": { type: String, defaultValue: "charisma" },
"charismaAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
rangedAttack: {type: Schemas.Skill},
"rangedAttack.ability": { type: String, defaultValue: "dexterity" },
"rangedAttack.proficiency": {
type: [Schemas.Effect],
defaultValue: [{name: "Attack Proficiency", value: 1, operation: "proficiency", type: "inate"}]
},
dexterityArmor: {type: Schemas.Skill},
"dexterityArmor.ability": { type: String, defaultValue: "dexterity" },
@@ -239,19 +213,22 @@ Schemas.Character = new SimpleSchema({
},
//mechanics
features: { type: [Schemas.Feature], defaultValue: []},
features: { type: [String], defaultValue: [], regEx: SimpleSchema.RegEx.Id,},
customFeatures: { type: [Schemas.Feature], defaultValue: []},
actions: { type: [Schemas.Action], defaultValue: []},
deathSave: { type: Schemas.DeathSave },
time: { type: Number, min: 0, decimal: true, defaultValue: 0},
initiativeOrder:{ type: Number, min: 0, max: 1, decimal: true, defaultValue: 0},
expirations: { type: [Schemas.Expiration], defaultValue: []},
spells: { type: [Schemas.Spell], defaultValue: []}
buffs: { type: [Schemas.Buff], defaultValue: []}
//TODO add permission stuff for owner, readers and writers
//TODO add per-character settings
});
Characters.attachSchema(Schemas.Character);
//reactively remove expired effects
//this can be optimised a lot once clients can do projections
//TODO broken with the move from expirations -> buffs
//TODO fix by finding every buff whose expiry is >= current time, pull those buffs
Characters.find({},{fields: {time: 1, expirations: 1, features: 1}}).observe({
changed: function(character){
var currentTime = character.time;
@@ -276,25 +253,38 @@ Characters.find({},{fields: {time: 1, expirations: 1, features: 1}}).observe({
});
var attributeBase = function(charId, attribute){
var effects = _.groupBy(attribute.effects, "operation");
var value = 0;
_.each(attribute.effects, function(effect){
switch(effect.operation) {
case "add":
value += evaluateEffect(charId, effect);
break;
case "mul":
value *= evaluateEffect(charId, effect);
break;
case "min":
var min = evaluateEffect(charId, effect);
value = value > min? value : min;
break;
case "max":
var max = evaluateEffect(charId, effect);
value = value < max? value : max;
break;
//start with the highest base value
_.each(effects.base, function(effect){
var efv = evaluateEffect(charId, effect)
if (effect.value > value){
value = effect.value;
}
});
//add all the add values
_.each(effects.add, function(effect){
value += evaluateEffect(charId, effect);
});
//multiply all the mul values
_.each(effects.mul, function(effect){
value *= evaluateEffect(charId, effect);
});
//ensure value is >= all mins
_.each(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
value = value > min? value : min;
});
//ensure value is <= all maxes
_.each(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
value = value < max? value : max;
});
return value;
}
@@ -309,7 +299,7 @@ Characters.helpers({
fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector});
var field = char[fieldName];
if(!field){
if(field === undefined){
throw new Meteor.Error("getField failed",
"getField could not find field " + fieldName + " in character "+ char._id);
}
@@ -333,35 +323,15 @@ Characters.helpers({
return this.getField(fieldName);
},
attributeValue: (function(){
//store a private array of attributes we've visited without returning
//if we try to visit the same attribute twice before resolving its value
//we are in a dependency loop and need to GTFO
var visitedAttributes = [];
return function(attributeName){
check(attributeName, String);
//we're still evaluating this attribute, must be in a loop
if(_.contains(visitedAttributes, attributeName)) {
console.log("dependency loop detected");
return NaN;
}
//push this attribute to the list of visited attributes
//we can't visit it again unless it returns first
visitedAttributes.push(attributeName);
try{
var charId = this._id;
var attribute = this.getField(attributeName);
//base value
var value = attributeBase(charId, attribute);
value += attribute.adjustment;
}finally{
//this attribute returns or fails, pull it from the array, we may visit it again safely
visitedAttributes = _.without(visitedAttributes, attributeName);
}
return value;
}
})(),
attributeValue: function(attributeName){
var charId = this._id;
var attribute = this.getField(attributeName);
//base value
var value = this.attributeBase(attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
},
attributeBase: (function(){
//store a private array of attributes we've visited without returning

View File

@@ -0,0 +1,42 @@
//Features are features that can be selected but not edited
//they are the things that come in the player's handbook and
//facilitate the quick building of characters
//They are the primary means of collecting cease and desist letters :(
//
//Should only be edited by admins
//
//TODO add a Meteor Method that lets users add a feature to their character
//and pushes the effects and actions accordingly
//
//TODO add a Method that updates every character with a given feature if that feature should change
Features = new Meteor.Collection("features");
Schemas.Feature = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}
},
name: {type: String},
description:{type: String, optional: true},
source: {type: String, optional: true},
effects: {type: [Schemas.Effect], defaultValue: []},
actions: {type: [Schemas.Action], defaultValue: []},
attacks: {type: [Schemas.Attack], defaultValue: []},
spells: {type: [Schemas.Spell] , defaultValue: []},
});
Features.attachSchema(Schemas.Feature);
//observe standard features for changes and update characters using them
Features.find().observe({
changed: function(newFeature, oldFeature){
//TODO
},
removed: function(oldFeature){
//TODO
}
});

View File

@@ -0,0 +1,29 @@
/*
* Actions are given to a character by items and features
*/
Schemas.Action = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}
},
name: {
type: String
},
description: {
type: String
},
type: {
type: String,
allowedValues: ["action, bonus, reaction, free"],
defaultValue: "action"
},
selfBuffs: {
type: [Schemas.Buff], defaultValue: []
},
selfAdjustments: {
type: [Schemas.Adjustment], defaultValue: []
}
});

View File

@@ -0,0 +1,25 @@
/*
* Adjustments make instantaneous changes to the value of some attribute
* 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,
optional: true
},
//the value added to the stat
value: {
type: Number,
decimal: true,
optional: true
},
calculation: {
type: String,
optional: true
}
});

View File

@@ -0,0 +1,31 @@
/*
* Attacks are given to a character by items and features
*/
Schemas.Attack = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}
},
name: {
type: String
},
range: {
type: String,
optional: true
},
attackBonus: {
type: String,
optional: true
},
damage: {
type: String
},
damageType: {
type: String,
allowedValues: ["acid", "bludgeoning", "cold", "fire", "force", "lightning", "necrotic",
"piercing", "poison", "psychic", "radiant", "slashing", "thunder"]
}
});

View File

@@ -0,0 +1,18 @@
//buffs are temporary once applied and store things which expire and their expiry time
Schemas.Buff = new SimpleSchema({
//buff id
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}},
//things that expire
effects: { type: [Schemas.Effect], defaultValue: [] },
actions: { type: [Schemas.Action], defaultValue: [] },
//expiry time
expiry: { type: Number, optional: true},
duration: { type: Number }
});

View File

@@ -1,5 +1,5 @@
/*
* Effects are reason-value pairs attached to skills and abilities
* Effects are reason-value attached to skills and abilities
* that modify their final value or presentation in some way
*/
Schemas.Effect = new SimpleSchema({
@@ -16,7 +16,7 @@ Schemas.Effect = new SimpleSchema({
operation: {
type: String,
defaultValue: "add",
allowedValues: ["proficiency","add","mul","min","max","advantage","disadvantage","passiveAdd","fail","conditional","passiveAdd"]
allowedValues: ["base", "proficiency","add","mul","min","max","advantage","disadvantage","passiveAdd","fail","conditional","passiveAdd"]
},
value: {
type: Number,
@@ -32,5 +32,10 @@ Schemas.Effect = new SimpleSchema({
type: String,
defaultValue: "editable",
allowedValues: ["editable", "feat", "buff", "equipment", "inate"]
},
//which stat the effect is applied to
stat: {
type: String,
optional: true
}
});

View File

@@ -1,12 +0,0 @@
/*
* A buff becomes an effect when applied on a creature.
* It is the effect plus the stat to which it should be applied
*/
Schemas.Buff = new SimpleSchema({
stat: {
type: String
},
effect: {
type: Schemas.Effect
}
});

View File

@@ -1,13 +0,0 @@
//schema to store all effects which expire and their expiry dates
Schemas.Expiration = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}},
stat: { type: String },
effectIds: { type: [String], regEx: SimpleSchema.RegEx.Id },
featureIds:{ type: [String], regEx: SimpleSchema.RegEx.Id },
expiry: { type: Number }
});

View File

@@ -1,22 +0,0 @@
Schemas.Feature = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}
},
name: {type: String},
description:{type: String},
source: {type: String},
buffs: {type: [Schemas.Buff], defaultValue: []},
enabled: {type: Boolean, defaultValue: false},
duration: {type: Number, optional: true},
uses: {type: Number, min: 0, optional: true},
maxUses: {type: Number, min: 0, optional: true},
reset: {
type: String,
optional: true,
allowedValues: ["longRest", "shortRest"]
}
});

View File

@@ -13,8 +13,10 @@ Schemas.Spell = new SimpleSchema({
duration: {type: Number},
"components.verbal": {type: Boolean},
"components.somatic": {type: Boolean},
"components.material": {type: String},
"components.material": {type: String, optional: true},
"components.concentration": {type: Boolean},
buffs: {type: [Schemas.Buff], optional: true},
ritual: {type: Boolean},
selfBuffs: {type: [Schemas.Buff], defaultValue: []},
level: {type: Number},
class: {type: String}
});

View File

@@ -4,18 +4,42 @@ Schemas.Item = new SimpleSchema({
name: {type: String, defaultValue: "New Item"},
plural: {type: String, optional: true},
description:{type: String, defaultValue: ""},
container: {type: String, regEx: SimpleSchema.RegEx.Id},
container: {type: String}, //id of container it normally is stowed in
character: {type: String, regEx: SimpleSchema.RegEx.Id}, //id of owner
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
tradeGood: {type: Boolean, defaultValue: false},
stackable: {type: Boolean, defaultValue: false},
buffs: {type: [Schemas.Buff], defaultValue: []},
equipmentSlot: {type: String, defaultValue: "", allowedValues: ["head", "body", "arms", "hands", "held", "feet"]},
feature: {type: Schemas.Feature},
equipmentSlot: {
type: String,
defaultValue: "none",
allowedValues: ["none", "head", "armor", "arms", "hands", "held", "feet"]
},
equipped: {type: Boolean, defaultValue: false}
});
Items.attachSchema(Schemas.Item);
//update the features of the items as needed
Items.find({}, {fields: {feature: 1, character: 1, equipped: 1}}).observe({
added: function(newItem){
if(newItem.feature && newItem.character)
addFeatureEffects(newItem.character, newItem.feature);
},
changed: function(newItem, oldItem){
if(oldItem.feature && oldItem.character)
removeFeatureEffects(oldItem.character, oldItem.feature);
if(newItem.feature && newItem.character)
addFeatureEffects(newItem.character, newItem.feature);
},
removed: function(oldItem){
if(oldItem.feature && oldItem.character)
removeFeatureEffects(oldItem.character, oldItem.feature);
}
});
Items.helpers({
totalValue: function(){
return this.value * this.quantity;

25
rpg-docs/bower.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "RPG Docs",
"version": "0.0.0",
"homepage": "",
"authors": ["Stefan Zermatten"],
"license": "none",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"polymer": "Polymer/polymer#~0.5.2",
"core-elements": "Polymer/core-elements#~0.5.2",
"paper-elements": "Polymer/paper-elements#~0.5.2"
},
"resolutions": {
"core-component-page": "^0.5.0",
"polymer": "^0.5.0",
"webcomponentsjs": "^0.5.0"
}
}

View File

@@ -58,7 +58,6 @@ paper-button {
}
.card {
width: 148px;
margin: 4px;
padding: 16px;
font-size: 14px;
@@ -67,7 +66,7 @@ paper-button {
}
.card.double {
width: 304px;
width: 332px;
}
.card paper-button {

View File

@@ -31,7 +31,7 @@ h1, .headline {
letter-spacing: 0;
}
.white-text h1, .white-text .headline{
.white-text h1, .white-text .headline, .white-text.headline{
color: rgba(255,255,255,0.87);
}

View File

@@ -1,8 +0,0 @@
<template name="features">
{{#each features}}
<li><strong>{{name}}</strong><input class="enabled" type="checkbox" checked={{enabled}}><br>
{{# each effects}}
{{stat}}: {{value}}
{{/each}}</li>
{{/each}}
</template>

View File

@@ -11,6 +11,7 @@
#stats .card {
flex-grow: 1;
max-width: 480px;
}
#stats .abilityFlex{
@@ -25,6 +26,26 @@
flex-grow: 1;
}
.attribute.card, .skill.card {
padding: 0;
display: flex;
flex-direction: column;
text-align: center;
}
.card-top {
flex-grow: 1;
padding: 16px;
border-radius: 2px 2px 0 0;
display: flex;
align-items: center;
justify-content: center;
}
.card .subhead {
padding: 16px;
}
.editEffect > * {
vertical-align: bottom;
}
@@ -35,6 +56,14 @@
padding: 16px;
}
#armorHeading {
background: url(/jpg/rusted-metal-armor.jpg) no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
#detailContainer {
position: fixed;
display: flex;

View File

@@ -4,73 +4,6 @@
<div class="abilityFlex">
{{> abilityCards}}
</div>
<div class="statsFlex">
<paper-shadow {{isHero "armor"}} class="card attribute" hero-id="armor">
<h1>{{attributeValue "armor"}}</h1>
<p class="caption">Armor</p>
</paper-shadow>
<paper-shadow {{isHero "initiative"}} class="card skill" hero-id="initiative">
<h1>{{skillMod "initiative"}}</h1>
<p class="caption">Initiative</p>
</paper-shadow>
<paper-shadow {{isHero "proficiencyBonus"}} class="card attribute" hero-id="proficiencyBonus">
<h1>{{attributeValue "proficiencyBonus"}}</h1>
<p class="caption">Proficiency Bonus</p>
</paper-shadow>
<paper-shadow {{isHero "speed"}} class="card attribute" hero-id="speed">
<h1>{{attributeValue "speed"}}</h1>
<p class="caption">Speed</p>
</paper-shadow>
<paper-shadow {{isHero "passivePerception"}} class="card" hero-id="passivePerception">
<h1>{{passiveSkill "perception"}}</h1>
<p class="caption">Passive Perception</p>
</paper-shadow>
<paper-shadow class="card" id="hitDice">
<h1 class="attribute" hero-id="d6HitDice" {{isHero "d6HitDice"}}>
{{> hitDice hitDice="d6HitDice" d="6"}}
</h1>
<h1 class="attribute" hero-id="d8HitDice" {{isHero "d8HitDice"}}>
{{> hitDice hitDice="d8HitDice" d="8"}}
</h1>
<h1 class="attribute" hero-id="d10HitDice" {{isHero "d10HitDice"}}>
{{> hitDice hitDice="d10HitDice" d="10"}}
</h1>
<h1 class="attribute" hero-id="d12HitDice" {{isHero "d12HitDice"}}>
{{> hitDice hitDice="d12HitDice" d="12"}}
</h1>
<p class="caption">Hit Dice</p>
</paper-shadow>
{{# if canCast}}
<paper-shadow class="card" id="spellSlots">
<h1>{{> spellSlots}}</h1>
<p class="caption">Spell Slots</p>
</paper-shadow>
{{/if}}
{{# if attributeBase "rages"}}
<paper-shadow class="card" id="rages">
<h1>{{attributeValue "rages"}}</h1>
<p class="caption">rages</p>
</paper-shadow>
{{/if}}
{{# if attributeBase "sorceryPoints"}}
<paper-shadow class="card" id="sorceryPoints">
<h1>{{attributeValue "sorceryPoints"}}</h1>
<p class="caption">Sorcery Points</p>
</paper-shadow>
{{/if}}
{{# if attributeBase "expertiseDice"}}
<paper-shadow class="card" id="expertiseDice">
<h1>{{attributeValue "expertiseDice"}}</h1>
<p class="caption">Expertise Dice</p>
</paper-shadow>
{{/if}}
{{# if attributeBase "superiorityDice"}}
<paper-shadow class="card" id="superiorityDice">
<h1>{{attributeValue "superiorityDice"}}</h1>
<p class="caption">Superiority Dice</p>
</paper-shadow>
{{/if}}
</div>
</section>
<section id="detailContainer">
<div cross-fade id="darkOverlay"></div>
@@ -80,12 +13,6 @@
</core-animated-pages>
</template>
<template name="hitDice">
{{# if ../attributeBase hitDice}}
{{../attributeValue hitDice}}d{{d}} + {{../abilityMod "constitution"}}
{{/if}}
</template>
<template name="spellSlots">
{{attributeValue "level1SpellSlots"}}
{{attributeValue "level2SpellSlots"}}
@@ -98,6 +25,17 @@
{{attributeValue "level9SpellSlots"}}
</template>
<template name="statCard">
<paper-shadow {{isHero id}} class="card {{type}}" hero-id={{id}}>
<div id="{{id}}Heading" class="card-top headline {{class}}">
{{> UI.contentBlock}}
</div>
<div class="subhead">
{{title}}
</div>
</paper-shadow>
</template>
<template name="attributeDialog">
<!--needs character, attributeName, attributeTitle-->
{{#if attributeName}}

View File

@@ -55,6 +55,15 @@ Template.stats.helpers({
}
});
Template.statCard.helpers({
isHero: function(string){
if(string === Session.get("selectedAttribute")||
string === Session.get("selectedSkill")){
return "hero";
}
}
});
Template.attributeDialog.helpers({
attributeTitle: function(){
return Session.get("selectedAttributeTitle");

View File

@@ -12,6 +12,7 @@
background-color: #D50000;
padding: 16px;
position: relative;
border-radius: 2px 0 0 2px;
}
.abilityFlex .card {
@@ -20,10 +21,11 @@
.abilityCardRight {
flex-grow: 1;
padding-right: 0;
}
.abilityCardRight hr{
margin: 8px -16px;
margin: 8px 0 8px -16px;
}
.abilityCardRight h1{

View File

@@ -33,6 +33,7 @@
<div class="abilityCardRight">
<h1>Dexterity</h1>
{{> skillRow name="Save" skill="dexteritySave"}}
{{> skillRow name="Initiative" skill="initiative"}}
<hr>
{{> skillRow name="Acrobatics" skill="acrobatics"}}
{{> skillRow name="Sleight of Hand" skill="sleightOfHand"}}
@@ -52,6 +53,18 @@
<h1>Constitution</h1>
{{> skillRow name="Save" skill="constitutionSave"}}
<hr>
<h1 class="attribute" hero-id="d6HitDice">
{{> hitDice hitDice="d6HitDice" d="6"}}
</h1>
<h1 class="attribute" hero-id="d8HitDice">
{{> hitDice hitDice="d8HitDice" d="8"}}
</h1>
<h1 class="attribute" hero-id="d10HitDice">
{{> hitDice hitDice="d10HitDice" d="10"}}
</h1>
<h1 class="attribute" hero-id="d12HitDice">
{{> hitDice hitDice="d12HitDice" d="12"}}
</h1>
</div>
</paper-shadow>
</template>
@@ -90,7 +103,7 @@
{{> skillRow name="Animal Handling" skill="animalHandling"}}
{{> skillRow name="Insight" skill="insight"}}
{{> skillRow name="Medicine" skill="medicine"}}
{{> skillRow name="Perception" skill="perception"}}
{{> skillRow name="Perception" skill="perception" showPassive="true"}}
{{> skillRow name="Survival" skill="survival"}}
</div>
</paper-shadow>
@@ -114,3 +127,9 @@
</div>
</paper-shadow>
</template>
<template name="hitDice">
{{# if ../attributeBase hitDice}}
{{../attributeValue hitDice}}d{{d}} {{../abilityMod "constitution"}}
{{/if}}
</template>

View File

@@ -3,7 +3,6 @@
<paper-tab>Stats</paper-tab>
<paper-tab>Features</paper-tab>
<paper-tab>Inventory</paper-tab>
Proficiency Bonus
<paper-tab>Spellbook</paper-tab>
<paper-tab>Persona</paper-tab>
<paper-tab>Journal</paper-tab>
@@ -11,7 +10,7 @@
<core-animated-pages id="tabPages" selected={{selectedTab}} transitions="slide-from-right">
<swipe-detect touch-action="pan-y">{{> stats}}</swipe-detect>
<swipe-detect touch-action="pan-y">features</swipe-detect>
<swipe-detect touch-action="pan-y">{{> features}}</swipe-detect>
<swipe-detect touch-action="pan-y">inventory</swipe-detect>
<swipe-detect touch-action="pan-y">spellBook</swipe-detect>
<swipe-detect touch-action="pan-y">persona</swipe-detect>

View File

@@ -0,0 +1,32 @@
<template name="features">
<div class="statsFlex"><!--resources-->
{{#if attributeBase "rages"}}
{{#statCard id="rages" type="attribute" title="Rages"}}
{{attributeValue "rages"}}
{{/statCard}}
{{/if}}
{{#if canCast}}
{{#statCard id="spellSlots" type="attribute" title="Spell Slots"}}
<h1>{{> spellSlots}}</h1>
{{/statCard}}
{{/if}}
{{#if attributeBase "sorceryPoints"}}
{{#statCard id="sorceryPoints" type="attribute" title="Sorcery Points"}}
{{attributeValue "sorceryPoints"}}
{{/statCard}}
{{/if}}
{{#if attributeBase "expertiseDice"}}
{{#statCard id="expertiseDice" type="attribute" title="Expertise Dice"}}
{{attributeValue "expertiseDice"}}
{{/statCard}}
{{/if}}
{{#if attributeBase "superiorityDice"}}
{{#statCard id="superiorityDice" type="attribute" title="Superiority Dice"}}
{{attributeValue "superiorityDice"}}
{{/statCard}}
{{/if}}
</div>
<div class="actionsFlex">
</div>
</template>

View File

@@ -1,10 +0,0 @@
<template name="containerTable">
<div>
<h3>{{name}}</h3>
<table>
{{#each items}}
{{> itemRow}}
{{/each}}
</table>
</div>
</template>

View File

@@ -1,5 +0,0 @@
Template.containerTable.helpers({
items: function(){
return Items.find({container: this._id});
}
});

View File

@@ -1,6 +1,20 @@
<template name="inventory">
<div class="flexItem floatBox">
<h2>Inventory</h2>
{{> inventoryTables}}
<paper-shadow class="equipment">
Armor: {{#if equippedArmor}}{{equippedArmor}}{{else}}None{{/if}}
</paper-shadow>
<div class="containers">
{{#each containers}}
<paper-shadow>
<h3>{{name}}</h3>
<table>
{{#each items}}
<tr class={{#if isSelected}}selected{{/if}}>
<td>{{#if stackable}}{{quantity}}{{/if}}</td>
<td>{{pluralName}}</td>
</tr>
{{/each}}
</table>
</paper-shadow>
{{/each}}
</div>
</template>

View File

@@ -1,5 +0,0 @@
<template name="inventoryTables">
{{#each containers}}
{{> containerTable}}
{{/each}}
</template>

View File

@@ -1,5 +0,0 @@
Template.inventoryTables.helpers({
containers: function(){
return Containers.find({owner: this._id});
}
});

View File

@@ -1,3 +0,0 @@
tr.selected{
background-color: #4182BC;
}

View File

@@ -1,6 +0,0 @@
<template name="itemRow">
<tr class={{#if isSelected}}selected{{/if}}>
<td>{{#if stackable}}{{quantity}}{{/if}}</td>
<td>{{pluralName}}</td>
</tr>
</template>

View File

@@ -1,15 +0,0 @@
Template.itemRow.helpers({
isSelected: function(){
return Session.get('selectedItemRow')=== this._id;
}
});
Template.itemRow.events({
"click": function(e){
if(Session.get('selectedItemRow')=== this._id){
Session.set('selectedItemRow', null);
} else{
Session.set('selectedItemRow', this._id);
}
}
});

View File

@@ -22,6 +22,11 @@
{{else}}
<div class="{{advantage}} skillMod">{{../skillMod skill}}</div>
{{/if}}
<div class="{{conditionals}} skillName">{{name}}</div>
<div class="{{conditionals}} skillName">
{{name}}
{{#if showPassive}}
({{../passiveSkill skill}})
{{/if}}
</div>
</div>
</template>

View File

@@ -1,42 +0,0 @@
//give a character a set of buffs that expire after [duration]
pushBuffs = function(id, buffArray, duration){
_.each(buffArray, function(buff){
var pushObject = {};
if(duration > 0){
//expiry time is now plus duration
var expiry = Characters.findOne(id, {fields: {time: 1}}).time + duration;
//ensure the effect has an id
buff.effect._id = buff.effect._id || Random.id();
//build the expiration object
var expiration = {
stat: buff.stat,
effectId: buff.effect._id,
expiry: expiry
};
//push expiration object to character
Characters.update(id, {$push: {expirations: expiration } });
}
//push the effect to the character
pushObject[buff.stat] = buff.effect;
Characters.update(id, {$push: pushObject});
});
};
//pull all the buffs listed in the buffArray
pullBuffs = function(id, buffArray){
_.each(buffArray, function(buff){
pullEffect(id, buff.effect._id);
});
};
//pull a single effect by stat and id
pullEffect = function(id, stat, effectId){
var pullObject = {};
pullObject[stat] = {_id: effectId};
Characters.update(id, {$pull: pullObject });
}
//pull an expiry by id
pullExpiry = function(id, expiryId){
Characters.update(id, {$pull: {expirations: {_id: expiryId} } });
}

View File

@@ -0,0 +1,22 @@
Meteor.methods({
updateAction: function (charId, oldAction, newAction) {
var selector = {_id: charId, "actions._id": oldAction._id};
var setter = {"actions.$": newAction};
Characters.update(
selector,
{ $set: setter }
);
}
});
pullAction = function(id, action){
var pullObject = {};
pullObject["actions"] = {_id: action._id};
Characters.update(id, {$pull: pullObject });
};
pushAction = function(id, action){
var pushObject = {};
pushObject["actions"] = action;
Characters.update(id, {$push: pushObject});
};

View File

@@ -0,0 +1,22 @@
Meteor.methods({
updateAttack: function (charId, oldAttack, newAttack) {
var selector = {_id: charId, "attacks._id": oldAttack._id};
var setter = {"attacks.$": newAttack};
Characters.update(
selector,
{ $set: setter }
);
}
});
pullAttack = function(id, attack){
var pullObject = {};
pullObject["attacks"] = {_id: attack._id};
Characters.update(id, {$pull: pullObject });
};
pushAttack = function(id, attack){
var pushObject = {};
pushObject["attacks"] = attack;
Characters.update(id, {$push: pushObject});
};

View File

@@ -0,0 +1,27 @@
Meteor.methods({
updateEffect: function (charId, attributeName, effectId, newEffect) {
var selector = {_id: charId};
selector[attributeName + ".effects._id"] = effectId;
var setter = {};
setter[attributeName + ".effects.$"] = newEffect
Characters.update(
selector,
{ $set: setter }
)
}
});
//pull a single effect by stat and id
pullEffect = function(id, effect){
var pullObject = {};
pullObject[effect.stat + ".effects"] = {_id: effect._id};
Characters.update(id, {$pull: pullObject });
}
pushEffect = function(id, effect){
var pushObject = {};
pushObject[effect.stat + ".effects"] = effect;
Characters.update(id, {$push: pushObject});
}

View File

@@ -0,0 +1,56 @@
Meteor.methods({
addFeature: function(charId, newFeature){
Characters.update(
charId,
{ $push: {"customFeatures": newFeature} }
);
addFeatureEffects(charId, newFeature);
},
removeFeature: function(charId, oldFeature){
Characters.update(
charId,
{ $pull: { "customFeatures": {"_id": oldFeature._id} } }
);
removeFeatureEffects(charId, oldFeature);
},
updateFeature: function (charId, oldFeature, newFeature) {
var selector = {_id: charId, "customFeatures._id": oldFeature._id};
var setter = {"customFeatures.$": newFeature};
Characters.update(
selector,
{ $set: setter }
);
removeFeatureEffects(charId, oldFeature);
addFeatureEffects(charId, newFeature);
}
});
addFeatureEffects = function(charId, newFeature){
_.each(newFeature.effects, function(effect){
pushEffect(charId, effect);
});
_.each(newFeature.actions, function(action){
pushAction(charId, action);
});
_.each(newFeature.attacks, function(attack){
pushAttack(charId, attack);
});
_.each(newFeature.spells, function(spell){
pushSpell(charId, spell);
});
}
removeFeatureEffects = function(charId, oldFeature){
_.each(oldFeature.effects, function(effect){
pullEffect(charId, effect);
});
_.each(oldFeature.actions, function(action){
pullAction(charId, action);
});
_.each(newFeature.attacks, function(attack){
pushAttack(charId, attack);
});
_.each(newFeature.spells, function(spell){
pushSpell(charId, spell);
});
};

View File

@@ -0,0 +1,22 @@
Meteor.methods({
updateSpell: function (charId, oldSpell, newSpell) {
var selector = {_id: charId, "spells._id": oldSpell._id};
var setter = {"spells.$": newSpell};
Characters.update(
selector,
{ $set: setter }
);
}
});
pullSpell = function(id, spell){
var pullObject = {};
pullObject["spells"] = {_id: spell._id};
Characters.update(id, {$pull: pullObject });
};
pushSpell = function(id, spell){
var pushObject = {};
pushObject["spells"] = spell;
Characters.update(id, {$push: pushObject});
};

View File

@@ -1,12 +0,0 @@
Meteor.methods({
updateEffect: function (charId, attributeName, effectId, newEffect) {
var selector = {_id: charId};
selector[attributeName + ".effects._id"] = effectId;
var setter = {};
setter[attributeName + ".effects.$"] = newEffect
Characters.update(
selector,
{ $set: setter }
)
}
});

View File

@@ -1,29 +0,0 @@
[[[[[ ~/workspace/rpg-docs ]]]]]
=> Started proxy.
=> Started MongoDB.
I20141122-15:44:13.239(0)? ** You've set up some data subscriptions with Meteor.publish(), but
I20141122-15:44:13.351(0)? ** you still have autopublish turned on. Because autopublish is still
I20141122-15:44:13.352(0)? ** on, your Meteor.publish() calls won't have much effect. All data
I20141122-15:44:13.352(0)? ** will still be sent to all clients.
I20141122-15:44:13.352(0)? **
I20141122-15:44:13.352(0)? ** Turn off autopublish by removing the autopublish package:
I20141122-15:44:13.353(0)? **
I20141122-15:44:13.353(0)? ** $ meteor remove autopublish
I20141122-15:44:13.353(0)? **
I20141122-15:44:13.353(0)? ** .. and make sure you have Meteor.publish() and Meteor.subscribe() calls
I20141122-15:44:13.353(0)? ** for each collection that you want clients to see.
I20141122-15:44:13.353(0)? 
=> Started your app.
=> App running at: http://localhost:3000/
Can't listen on port 3000. Perhaps another Meteor is running?
Running two copies of Meteor in the same application directory
will not work. If something else is using port 3000, you can
specify an alternative port with --port <port>.
Can't listen on port 3000. Perhaps another Meteor is running?
Running two copies of Meteor in the same application directory
will not work. If something else is using port 3000, you can
specify an alternative port with --port <port>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB