Compare commits
5 Commits
2.0-beta.1
...
2.0-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4dc5895aa | ||
|
|
cffe0ee574 | ||
|
|
ce51be7b8e | ||
|
|
315073bd8e | ||
|
|
50b99ef54f |
@@ -52,3 +52,4 @@ bozhao:link-accounts
|
|||||||
peerlibrary:reactive-publish
|
peerlibrary:reactive-publish
|
||||||
simple:rest
|
simple:rest
|
||||||
simple:rest-method-mixin
|
simple:rest-method-mixin
|
||||||
|
mikowals:batch-insert
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ meteorhacks:subs-manager@1.6.4
|
|||||||
meteortesting:browser-tests@1.3.3
|
meteortesting:browser-tests@1.3.3
|
||||||
meteortesting:mocha@1.1.5
|
meteortesting:mocha@1.1.5
|
||||||
meteortesting:mocha-core@7.0.1
|
meteortesting:mocha-core@7.0.1
|
||||||
|
mikowals:batch-insert@1.1.9
|
||||||
minifier-css@1.5.0
|
minifier-css@1.5.0
|
||||||
minifier-js@2.6.0
|
minifier-js@2.6.0
|
||||||
minimongo@1.6.0
|
minimongo@1.6.0
|
||||||
|
|||||||
11
app/imports/api/campaign/Campaigns.js
Normal file
11
app/imports/api/campaign/Campaigns.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
|
let Campaigns = new Mongo.Collection('campaigns');
|
||||||
|
|
||||||
|
let CampaignSchema = new SimpleSchema({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Campaigns.attachSchema(CampaignSchema);
|
||||||
|
|
||||||
|
export default Campaigns;
|
||||||
@@ -1,9 +1,51 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let Encounters = new Mongo.Collection("encounters");
|
let Encounters = new Mongo.Collection('encounters');
|
||||||
|
|
||||||
|
const CreatureInitiativeSchema = new SimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
initiativeRoll: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const InitiativeSchema = new SimpleSchema({
|
||||||
|
// An ordered list of all creatures in the initiative order
|
||||||
|
creatures: {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
'creatures.$': {
|
||||||
|
type: CreatureInitiativeSchema,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
roundNumber: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
initiativeNumber: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// A creature can be in one ecounter at a time.
|
||||||
|
// All creatures in an encounter have a shared time and space.
|
||||||
let EncounterSchema = new SimpleSchema({
|
let EncounterSchema = new SimpleSchema({
|
||||||
//an encounter is a single flow of time all parties in an encounter are in-sync time wise
|
name: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
initiative: {
|
||||||
|
type: InitiativeSchema,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Encounters.attachSchema(EncounterSchema);
|
Encounters.attachSchema(EncounterSchema);
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let Parties = new Mongo.Collection("parties");
|
let Parties = new Mongo.Collection('parties');
|
||||||
|
|
||||||
let partySchema = new SimpleSchema({
|
let partySchema = new SimpleSchema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "New Party",
|
defaultValue: 'New Party',
|
||||||
trim: false,
|
trim: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
characters: {
|
creatures: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
},
|
},
|
||||||
characters: {
|
'creatures.$': {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
index: 1,
|
|
||||||
},
|
},
|
||||||
owner: {
|
owner: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -26,24 +25,4 @@ let partySchema = new SimpleSchema({
|
|||||||
|
|
||||||
Parties.attachSchema(partySchema);
|
Parties.attachSchema(partySchema);
|
||||||
|
|
||||||
Parties.allow({
|
|
||||||
insert: function(userId, doc) {
|
|
||||||
return userId && doc.owner === userId;
|
|
||||||
},
|
|
||||||
update: function(userId, doc, fields, modifier) {
|
|
||||||
return userId && doc.owner === userId;
|
|
||||||
},
|
|
||||||
remove: function(userId, doc) {
|
|
||||||
return userId && doc.owner === userId;
|
|
||||||
},
|
|
||||||
fetch: ["owner"],
|
|
||||||
});
|
|
||||||
|
|
||||||
Parties.deny({
|
|
||||||
update: function(userId, docs, fields, modifier) {
|
|
||||||
// can't change owners
|
|
||||||
return _.contains(fields, "owner");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Parties;
|
export default Parties;
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
|||||||
'ancestors.id': nodeId,
|
'ancestors.id': nodeId,
|
||||||
removed: {$ne: true},
|
removed: {$ne: true},
|
||||||
}).fetch();
|
}).fetch();
|
||||||
// The root node is last in the array of nodes
|
// The root node is last in the array of nodes
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
|
|
||||||
// re-map all the ancestors
|
// re-map all the ancestors
|
||||||
@@ -181,17 +181,16 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Insert the creature properties
|
// Insert the creature properties
|
||||||
let docId;
|
let insertedDocIds = CreatureProperties.batchInsert(nodes);
|
||||||
nodes.forEach(doc => {
|
|
||||||
docId = CreatureProperties.insert(doc);
|
// get the root inserted doc
|
||||||
});
|
let rootId = insertedDocIds[insertedDocIds.length - 1];
|
||||||
|
|
||||||
// Recompute the creatures doc was attached to
|
// Recompute the creatures doc was attached to
|
||||||
let doc = CreatureProperties.findOne(docId);
|
recomputeCreatures(node);
|
||||||
recomputeCreatures(doc);
|
|
||||||
|
|
||||||
// Return the docId of the last property, the inserted root property
|
// Return the docId of the last property, the inserted root property
|
||||||
return docId;
|
return rootId;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
5
app/imports/api/creature/actions/applyAction.js
Normal file
5
app/imports/api/creature/actions/applyAction.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import spendResources from '/imports/api/creature/actions/spendResources.js'
|
||||||
|
|
||||||
|
export default function applyAction({prop}){
|
||||||
|
spendResources(prop);
|
||||||
|
}
|
||||||
61
app/imports/api/creature/actions/applyBuff.js
Normal file
61
app/imports/api/creature/actions/applyBuff.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
setLineageOfDocs,
|
||||||
|
renewDocIds
|
||||||
|
} from '/imports/api/parenting/parenting.js';
|
||||||
|
import {setDocToLastOrder} from '/imports/api/parenting/order.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||||
|
|
||||||
|
export default function applyBuff({
|
||||||
|
prop,
|
||||||
|
children,
|
||||||
|
creature,
|
||||||
|
targets = [],
|
||||||
|
//actionContext,
|
||||||
|
}){
|
||||||
|
let buffTargets = prop.target === 'self' ? [creature] : targets;
|
||||||
|
|
||||||
|
//let scope = {
|
||||||
|
// ...creature.variables,
|
||||||
|
// ...actionContext,
|
||||||
|
//};
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// If the target is not self, walk through all decendants and replace
|
||||||
|
// variables in calculations with their values from the creature scope
|
||||||
|
// If the target is self, replace all the target.x references with just x
|
||||||
|
|
||||||
|
// Then copy the decendants of the buff to the targets
|
||||||
|
prop.applied = true;
|
||||||
|
let propList = [prop];
|
||||||
|
function addChildrenToPropList(children){
|
||||||
|
children.forEach(child => {
|
||||||
|
propList.push(child.node);
|
||||||
|
addChildrenToPropList(child.children);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addChildrenToPropList(children);
|
||||||
|
let oldParent = {
|
||||||
|
id: prop.parent.id,
|
||||||
|
collection: prop.parent.collection,
|
||||||
|
};
|
||||||
|
buffTargets.forEach(target => {
|
||||||
|
copyNodeListToTarget(propList, target, oldParent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyNodeListToTarget(propList, target, oldParent){
|
||||||
|
let ancestry = [{collection: 'creatures', id: target._id}];
|
||||||
|
setLineageOfDocs({
|
||||||
|
docArray: propList,
|
||||||
|
newAncestry: ancestry,
|
||||||
|
oldParent,
|
||||||
|
});
|
||||||
|
renewDocIds({
|
||||||
|
docArray: propList,
|
||||||
|
});
|
||||||
|
setDocToLastOrder({
|
||||||
|
collection: CreatureProperties,
|
||||||
|
doc: propList[0],
|
||||||
|
});
|
||||||
|
CreatureProperties.batchInsert(propList);
|
||||||
|
}
|
||||||
19
app/imports/api/creature/actions/applyDamage.js
Normal file
19
app/imports/api/creature/actions/applyDamage.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||||
|
|
||||||
|
export default function applyDamage({
|
||||||
|
prop,
|
||||||
|
creature,
|
||||||
|
targets,
|
||||||
|
actionContext
|
||||||
|
}){
|
||||||
|
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
||||||
|
let scope = {
|
||||||
|
...creature.variables,
|
||||||
|
...actionContext,
|
||||||
|
};
|
||||||
|
let {result, errors} = evaluateString(prop.amount, scope);
|
||||||
|
if (Meteor.isClient) errors.forEach(e => console.error(e));
|
||||||
|
if (Number.isFinite(result)) {
|
||||||
|
damageTargets.forEach()
|
||||||
|
}
|
||||||
|
}
|
||||||
62
app/imports/api/creature/actions/applyProperties.js
Normal file
62
app/imports/api/creature/actions/applyProperties.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import applyAction from '/imports/api/creature/actions/applyAction.js';
|
||||||
|
//import applyDamage from '/imports/api/creature/actions/applyDamage.js';
|
||||||
|
import applyBuff from '/imports/api/creature/actions/applyBuff.js';
|
||||||
|
|
||||||
|
function applyProperty(options){
|
||||||
|
let prop = options.prop;
|
||||||
|
if (
|
||||||
|
prop.disabled === true || // ignore disabled props
|
||||||
|
prop.equipped === false || // ignore unequipped items
|
||||||
|
prop.toggleResult === false || // ignore untoggled toggles
|
||||||
|
prop.applied === true // ignore buffs that are already applied
|
||||||
|
){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (prop.type){
|
||||||
|
case 'action':
|
||||||
|
case 'spell':
|
||||||
|
case 'attack':
|
||||||
|
applyAction(options);
|
||||||
|
return true;
|
||||||
|
case 'damage':
|
||||||
|
// applyDamage(options);
|
||||||
|
return true;
|
||||||
|
case 'adjustment':
|
||||||
|
// applyAdjustment(options);
|
||||||
|
return true;
|
||||||
|
case 'buff':
|
||||||
|
applyBuff(options);
|
||||||
|
return false;
|
||||||
|
case 'roll':
|
||||||
|
// applyRoll(options);
|
||||||
|
return true;
|
||||||
|
case 'savingThrow':
|
||||||
|
// applySavingThrow(options);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function applyProperties({
|
||||||
|
forest,
|
||||||
|
creature,
|
||||||
|
targets,
|
||||||
|
actionContext
|
||||||
|
}){
|
||||||
|
forest.forEach(child => {
|
||||||
|
let walkChildren = applyProperty({
|
||||||
|
prop: child.node,
|
||||||
|
children: child.children,
|
||||||
|
creature,
|
||||||
|
targets,
|
||||||
|
actionContext
|
||||||
|
});
|
||||||
|
if (walkChildren){
|
||||||
|
applyProperties({
|
||||||
|
forest: child.children,
|
||||||
|
creature,
|
||||||
|
targets,
|
||||||
|
actionContext
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,88 +1,61 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import CreatureProperties, { getCreature, damagePropertyWork, adjustQuantityWork } from '/imports/api/creature/CreatureProperties.js';
|
import CreatureProperties, { getCreature } from '/imports/api/creature/CreatureProperties.js';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/recomputeCreature.js';
|
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/recomputeCreature.js';
|
||||||
|
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||||
|
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doAction',
|
name: 'creatureProperties.doAction',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
actionId: SimpleSchema.RegEx.Id,
|
actionId: SimpleSchema.RegEx.Id,
|
||||||
|
targetId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({actionId}) {
|
run({actionId, targetId}) {
|
||||||
let action = CreatureProperties.findOne(actionId);
|
let action = CreatureProperties.findOne(actionId);
|
||||||
// Check permissions
|
// Check permissions
|
||||||
let creature = getCreature(action);
|
let creature = getCreature(action);
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
doActionWork(action);
|
let target = undefined;
|
||||||
|
if (targetId) {
|
||||||
|
target = getCreature(targetId);
|
||||||
|
assertEditPermission(target, this.userId);
|
||||||
|
}
|
||||||
|
doActionWork({action, creature, target});
|
||||||
// Note this only recomputes the top-level creature, not the nearest one
|
// Note this only recomputes the top-level creature, not the nearest one
|
||||||
recomputeCreatureByDoc(creature);
|
recomputeCreatureByDoc(creature);
|
||||||
|
if (target){
|
||||||
|
recomputeCreatureByDoc(target);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function doActionWork(action){
|
function doActionWork({action, creature, target}){
|
||||||
spendResources(action);
|
let actionContext = {};
|
||||||
}
|
let decendantForest = nodesToTree({
|
||||||
|
collection: CreatureProperties,
|
||||||
function spendResources(action){
|
ancestorId: action._id,
|
||||||
// Check Uses
|
|
||||||
if (action.usesUsed >= action.usesResult){
|
|
||||||
throw new Meteor.Error('Insufficient Uses',
|
|
||||||
'This action has no uses left');
|
|
||||||
}
|
|
||||||
// Resources
|
|
||||||
if (action.insufficientResources){
|
|
||||||
throw new Meteor.Error('Insufficient Resources',
|
|
||||||
'This creature doesn\'t have sufficient resources to perform this action');
|
|
||||||
}
|
|
||||||
// Items
|
|
||||||
let itemQuantityAdjustments = [];
|
|
||||||
action.resources.itemsConsumed.forEach(itemConsumed => {
|
|
||||||
if (!itemConsumed.itemId){
|
|
||||||
throw new Meteor.Error('Ammo not selected',
|
|
||||||
'No ammo was selected for this action');
|
|
||||||
}
|
|
||||||
let item = CreatureProperties.findOne(itemConsumed.itemId);
|
|
||||||
if (!item || item.ancestors[0].id !== action.ancestors[0].id){
|
|
||||||
throw new Meteor.Error('Ammo not found',
|
|
||||||
'The action\'s ammo was not found on the creature');
|
|
||||||
}
|
|
||||||
if (!item.equipped){
|
|
||||||
throw new Meteor.Error('Ammo not equipped',
|
|
||||||
'The selected ammo is not equipped');
|
|
||||||
}
|
|
||||||
if (!itemConsumed.quantity) return;
|
|
||||||
itemQuantityAdjustments.push({
|
|
||||||
property: item,
|
|
||||||
operation: 'increment',
|
|
||||||
value: itemConsumed.quantity,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
// No more errors should be thrown after this line
|
let startingForest = [{
|
||||||
// Now that we have confirmed that there are no errors, do actual work
|
node: action,
|
||||||
//Items
|
children: decendantForest,
|
||||||
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
}];
|
||||||
// Use uses
|
applyProperties({
|
||||||
CreatureProperties.update(action._id, {
|
forest: startingForest,
|
||||||
$inc: {usesUsed: 1}
|
creature,
|
||||||
}, {
|
target,
|
||||||
selector: action
|
actionContext
|
||||||
});
|
|
||||||
// Damage stats
|
|
||||||
action.resources.attributesConsumed.forEach(attConsumed => {
|
|
||||||
if (!attConsumed.quantity) return;
|
|
||||||
let stat = CreatureProperties.findOne(attConsumed.statId);
|
|
||||||
damagePropertyWork({
|
|
||||||
property: stat,
|
|
||||||
operation: 'increment',
|
|
||||||
value: attConsumed.quantity,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
app/imports/api/creature/actions/spendResources.js
Normal file
57
app/imports/api/creature/actions/spendResources.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import CreatureProperties, { damagePropertyWork, adjustQuantityWork } from '/imports/api/creature/CreatureProperties.js';
|
||||||
|
|
||||||
|
export default function spendResources(action){
|
||||||
|
// Check Uses
|
||||||
|
if (action.usesUsed >= action.usesResult){
|
||||||
|
throw new Meteor.Error('Insufficient Uses',
|
||||||
|
'This action has no uses left');
|
||||||
|
}
|
||||||
|
// Resources
|
||||||
|
if (action.insufficientResources){
|
||||||
|
throw new Meteor.Error('Insufficient Resources',
|
||||||
|
'This creature doesn\'t have sufficient resources to perform this action');
|
||||||
|
}
|
||||||
|
// Items
|
||||||
|
let itemQuantityAdjustments = [];
|
||||||
|
action.resources.itemsConsumed.forEach(itemConsumed => {
|
||||||
|
if (!itemConsumed.itemId){
|
||||||
|
throw new Meteor.Error('Ammo not selected',
|
||||||
|
'No ammo was selected for this action');
|
||||||
|
}
|
||||||
|
let item = CreatureProperties.findOne(itemConsumed.itemId);
|
||||||
|
if (!item || item.ancestors[0].id !== action.ancestors[0].id){
|
||||||
|
throw new Meteor.Error('Ammo not found',
|
||||||
|
'The action\'s ammo was not found on the creature');
|
||||||
|
}
|
||||||
|
if (!item.equipped){
|
||||||
|
throw new Meteor.Error('Ammo not equipped',
|
||||||
|
'The selected ammo is not equipped');
|
||||||
|
}
|
||||||
|
if (!itemConsumed.quantity) return;
|
||||||
|
itemQuantityAdjustments.push({
|
||||||
|
property: item,
|
||||||
|
operation: 'increment',
|
||||||
|
value: itemConsumed.quantity,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// No more errors should be thrown after this line
|
||||||
|
// Now that we have confirmed that there are no errors, do actual work
|
||||||
|
//Items
|
||||||
|
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
||||||
|
// Use uses
|
||||||
|
CreatureProperties.update(action._id, {
|
||||||
|
$inc: {usesUsed: 1}
|
||||||
|
}, {
|
||||||
|
selector: action
|
||||||
|
});
|
||||||
|
// Damage stats
|
||||||
|
action.resources.attributesConsumed.forEach(attConsumed => {
|
||||||
|
if (!attConsumed.quantity) return;
|
||||||
|
let stat = CreatureProperties.findOne(attConsumed.statId);
|
||||||
|
damagePropertyWork({
|
||||||
|
property: stat,
|
||||||
|
operation: 'increment',
|
||||||
|
value: attConsumed.quantity,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -71,8 +71,8 @@ const findIcons = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 20,
|
||||||
timeInterval: 5000,
|
timeInterval: 10000,
|
||||||
},
|
},
|
||||||
run({search}){
|
run({search}){
|
||||||
if (!search) return [];
|
if (!search) return [];
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export function renewDocIds({docArray, collectionMap}){
|
|||||||
const remapReference = ref => {
|
const remapReference = ref => {
|
||||||
if (idMap[ref.id]){
|
if (idMap[ref.id]){
|
||||||
ref.id = idMap[ref.id];
|
ref.id = idMap[ref.id];
|
||||||
ref.collection = collectionMap[ref.collection] || ref.collection;
|
ref.collection = collectionMap && collectionMap[ref.collection] || ref.collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
docArray.forEach(doc => {
|
docArray.forEach(doc => {
|
||||||
@@ -204,17 +204,11 @@ export function getName(doc){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nodesToTree({collection, ancestorId, filter, options}){
|
export function nodeArrayToTree(nodes){
|
||||||
// Store a dict of all the nodes
|
// Store a dict of all the nodes
|
||||||
let nodeIndex = {};
|
let nodeIndex = {};
|
||||||
let nodeList = [];
|
let nodeList = [];
|
||||||
if (!options) options = {};
|
nodes.forEach( node => {
|
||||||
options.sort = {order: 1};
|
|
||||||
collection.find({
|
|
||||||
'ancestors.id': ancestorId,
|
|
||||||
removed: {$ne: true},
|
|
||||||
...filter,
|
|
||||||
}, options).forEach( node => {
|
|
||||||
let treeNode = {
|
let treeNode = {
|
||||||
node: node,
|
node: node,
|
||||||
children: [],
|
children: [],
|
||||||
@@ -238,3 +232,14 @@ export function nodesToTree({collection, ancestorId, filter, options}){
|
|||||||
});
|
});
|
||||||
return forest;
|
return forest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nodesToTree({collection, ancestorId, filter, options}){
|
||||||
|
if (!options) options = {};
|
||||||
|
options.sort = {order: 1};
|
||||||
|
let nodes = collection.find({
|
||||||
|
'ancestors.id': ancestorId,
|
||||||
|
removed: {$ne: true},
|
||||||
|
...filter,
|
||||||
|
}, options);
|
||||||
|
return nodeArrayToTree(nodes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ export default {
|
|||||||
this.ackErrors = null;
|
this.ackErrors = null;
|
||||||
} else if (typeof error === 'string'){
|
} else if (typeof error === 'string'){
|
||||||
this.ackErrors = error;
|
this.ackErrors = error;
|
||||||
} else {
|
} else if (error.reason){
|
||||||
|
this.ackErrors = error.reason;
|
||||||
|
} else {
|
||||||
this.ackErrors = 'Something went wrong'
|
this.ackErrors = 'Something went wrong'
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,39 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="appliedBuffs.length"
|
||||||
|
class="buffs"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-subheader>Buffs and conditions</v-subheader>
|
||||||
|
<v-list-tile
|
||||||
|
v-for="buff in appliedBuffs"
|
||||||
|
:key="buff._id"
|
||||||
|
:data-id="buff._id"
|
||||||
|
@click="clickProperty({_id: buff._id})"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
{{ buff.name }}
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
flat
|
||||||
|
@click.stop="softRemove(buff._id)"
|
||||||
|
>
|
||||||
|
<v-icon>delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ability-scores">
|
<div class="ability-scores">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -165,6 +198,30 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="action in actions"
|
||||||
|
:key="action._id"
|
||||||
|
class="actions"
|
||||||
|
>
|
||||||
|
<action-card
|
||||||
|
:model="action"
|
||||||
|
:data-id="action._id"
|
||||||
|
@click="clickProperty({_id: action._id})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="attack in attacks"
|
||||||
|
:key="attack._id"
|
||||||
|
class="attacks"
|
||||||
|
>
|
||||||
|
<action-card
|
||||||
|
attack
|
||||||
|
:model="attack"
|
||||||
|
:data-id="attack._id"
|
||||||
|
@click="clickProperty({_id: attack._id})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="weapons && weapons.length"
|
v-if="weapons && weapons.length"
|
||||||
class="weapon-proficiencies"
|
class="weapon-proficiencies"
|
||||||
@@ -187,7 +244,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="armors && armors.length"
|
v-if="armors && armors.length"
|
||||||
class="weapon-proficiencies"
|
class="armor-proficiencies"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -207,7 +264,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="tools && tools.length"
|
v-if="tools && tools.length"
|
||||||
class="weapon-proficiencies"
|
class="tool-proficiencies"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -245,37 +302,13 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="action in actions"
|
|
||||||
:key="action._id"
|
|
||||||
class="actions"
|
|
||||||
>
|
|
||||||
<action-card
|
|
||||||
:model="action"
|
|
||||||
:data-id="action._id"
|
|
||||||
@click="clickProperty({_id: action._id})"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="attack in attacks"
|
|
||||||
:key="attack._id"
|
|
||||||
class="attacks"
|
|
||||||
>
|
|
||||||
<action-card
|
|
||||||
attack
|
|
||||||
:model="attack"
|
|
||||||
:data-id="attack._id"
|
|
||||||
@click="clickProperty({_id: attack._id})"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</column-layout>
|
</column-layout>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
import { damageProperty } from '/imports/api/creature/CreatureProperties.js';
|
import { damageProperty, softRemoveProperty } from '/imports/api/creature/CreatureProperties.js';
|
||||||
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
||||||
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
||||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
@@ -381,6 +414,9 @@
|
|||||||
actions(){
|
actions(){
|
||||||
return getProperties(this.creature, {type: 'action'});
|
return getProperties(this.creature, {type: 'action'});
|
||||||
},
|
},
|
||||||
|
appliedBuffs(){
|
||||||
|
return getProperties(this.creature, {type: 'buff', applied: true});
|
||||||
|
},
|
||||||
attacks(){
|
attacks(){
|
||||||
let props = getProperties(this.creature, {type: 'attack'}).map(attack => {
|
let props = getProperties(this.creature, {type: 'attack'}).map(attack => {
|
||||||
attack.children = getActiveProperties({
|
attack.children = getActiveProperties({
|
||||||
@@ -409,6 +445,11 @@
|
|||||||
if (!obj) return 0;
|
if (!obj) return 0;
|
||||||
return Object.keys(obj).length;
|
return Object.keys(obj).length;
|
||||||
},
|
},
|
||||||
|
softRemove(_id){
|
||||||
|
softRemoveProperty.call({_id}, error => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
:error-messages="errors.description"
|
:error-messages="errors.description"
|
||||||
@change="change('description', ...arguments)"
|
@change="change('description', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
<!-- Duration not implemented yet
|
||||||
<text-field
|
<text-field
|
||||||
label="Duration"
|
label="Duration"
|
||||||
hint="How long the buff lasts"
|
hint="How long the buff lasts"
|
||||||
@@ -20,6 +21,16 @@
|
|||||||
:error-messages="errors.duration"
|
:error-messages="errors.duration"
|
||||||
@change="change('duration', ...arguments)"
|
@change="change('duration', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
-->
|
||||||
|
<smart-select
|
||||||
|
label="Target"
|
||||||
|
:hint="targetOptionHint"
|
||||||
|
:items="targetOptions"
|
||||||
|
:value="model.target"
|
||||||
|
:error-messages="errors.target"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
@change="change('target', ...arguments)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user