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

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