Implemented Features and Items granting effects, actions, attacks and spells
This commit is contained in:
@@ -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
|
||||
|
||||
42
rpg-docs/Model/Character/Features.js
Normal file
42
rpg-docs/Model/Character/Features.js
Normal 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
|
||||
}
|
||||
});
|
||||
29
rpg-docs/Model/Character/SubSchemas/Action.js
Normal file
29
rpg-docs/Model/Character/SubSchemas/Action.js
Normal 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: []
|
||||
}
|
||||
});
|
||||
25
rpg-docs/Model/Character/SubSchemas/Adjustment.js
Normal file
25
rpg-docs/Model/Character/SubSchemas/Adjustment.js
Normal 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
|
||||
}
|
||||
});
|
||||
31
rpg-docs/Model/Character/SubSchemas/Attack.js
Normal file
31
rpg-docs/Model/Character/SubSchemas/Attack.js
Normal 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"]
|
||||
}
|
||||
});
|
||||
18
rpg-docs/Model/Character/SubSchemas/Buff.js
Normal file
18
rpg-docs/Model/Character/SubSchemas/Buff.js
Normal 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 }
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
@@ -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 }
|
||||
});
|
||||
@@ -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"]
|
||||
}
|
||||
});
|
||||
@@ -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}
|
||||
});
|
||||
Reference in New Issue
Block a user