Merge branch 'develop' of https://github.com/ThaumRystra/DiceCloud into develop
This commit is contained in:
@@ -45,7 +45,7 @@ geojson-utils@1.0.11
|
||||
google-oauth@1.4.4
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.1.4
|
||||
htmljs@1.2.0
|
||||
htmljs@1.2.1
|
||||
http@2.0.0
|
||||
id-map@1.1.1
|
||||
inter-process-messaging@0.1.1
|
||||
@@ -72,7 +72,7 @@ mobile-status-bar@1.1.0
|
||||
modern-browsers@0.1.10
|
||||
modules@0.20.0
|
||||
modules-runtime@0.13.1
|
||||
mongo@1.16.8
|
||||
mongo@1.16.9
|
||||
mongo-decimal@0.1.3
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.8
|
||||
@@ -80,7 +80,7 @@ npm-mongo@4.17.2
|
||||
oauth@2.2.1
|
||||
oauth2@1.3.2
|
||||
ordered-dict@1.1.0
|
||||
ostrio:cookies@2.8.0
|
||||
ostrio:cookies@2.8.1
|
||||
ostrio:files@2.3.3
|
||||
patreon-oauth@0.1.0
|
||||
peerlibrary:assert@0.3.0
|
||||
@@ -112,7 +112,7 @@ shell-server@0.5.0
|
||||
simple:json-routes@2.3.1
|
||||
simple:rest@1.2.1
|
||||
simple:rest-bearer-token-parser@1.1.1
|
||||
simple:rest-json-error-handler@1.1.1
|
||||
simple:rest-json-error-handler@1.1.3
|
||||
simple:rest-method-mixin@1.1.0
|
||||
socket-stream-client@0.5.2
|
||||
spacebars-compiler@1.3.2
|
||||
@@ -128,4 +128,4 @@ webapp@1.13.8
|
||||
webapp-hashing@1.1.1
|
||||
zer0th:meteor-vuetify-loader@0.1.41
|
||||
zodern:fix-async-stubs@1.0.2
|
||||
zodern:types@1.0.11
|
||||
zodern:types@1.0.13
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
@font-face {
|
||||
font-family: "game-icons";
|
||||
src: url("/fonts/game-icons.eot?817af6e52c83163eb30ece54d9f7d16d?#iefix") format("embedded-opentype"),
|
||||
url("/fonts/game-icons.woff?817af6e52c83163eb30ece54d9f7d16d") format("woff"),
|
||||
url("/fonts/game-icons.ttf?817af6e52c83163eb30ece54d9f7d16d") format("truetype");
|
||||
url("/fonts/game-icons.woff?817af6e52c83163eb30ece54d9f7d16d") format("woff"),
|
||||
url("/fonts/game-icons.ttf?817af6e52c83163eb30ece54d9f7d16d") format("truetype");
|
||||
}
|
||||
|
||||
.game-icon {
|
||||
|
||||
@@ -22,7 +22,7 @@ const insertCreatureFolder = new ValidatedMethod({
|
||||
owner: userId
|
||||
}, {
|
||||
fields: { order: 1 },
|
||||
sort: { order: -1 }
|
||||
sort: { left: -1 }
|
||||
});
|
||||
if (existingFolders.count() >= 50) {
|
||||
throw new Meteor.Error('creatureFolders.methods.insert.denied',
|
||||
|
||||
@@ -31,7 +31,7 @@ const reorderCreatureFolder = new ValidatedMethod({
|
||||
owner: userId
|
||||
}, {
|
||||
fields: { order: 1, },
|
||||
sort: { order: -1 }
|
||||
sort: { left: -1 }
|
||||
}).forEach((folder, index) => {
|
||||
if (folder.order !== index) {
|
||||
CreatureFolders.update(_id, { $set: { order: index } })
|
||||
|
||||
@@ -55,7 +55,7 @@ const duplicateProperty = new ValidatedMethod({
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
|
||||
// Alert the user if the limit was hit
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function getParentRefByTag(creatureId, tag) {
|
||||
inactive: { $ne: true },
|
||||
tags: tag,
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
return prop && { id: prop._id, collection: 'creatureProperties' };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import '/imports/api/creature/creatureProperties/methods/adjustQuantity';
|
||||
import '/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary';
|
||||
import '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import '/imports/api/creature/creatureProperties/methods/duplicateProperty';
|
||||
import '/imports/api/creature/creatureProperties/methods/equipItem';
|
||||
import '/imports/api/creature/creatureProperties/methods/insertProperty';
|
||||
|
||||
@@ -159,7 +159,7 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) {
|
||||
...getFilter.descendants(referencedNode),
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
|
||||
// We are adding the referenced node and its descendants
|
||||
|
||||
@@ -23,13 +23,7 @@ const restoreProperty = new ValidatedMethod({
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Do work
|
||||
restore({
|
||||
_id,
|
||||
collection: CreatureProperties,
|
||||
extraUpdates: {
|
||||
$set: { dirty: true }
|
||||
},
|
||||
});
|
||||
restore(CreatureProperties, property, { $set: { dirty: true } });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const softRemoveProperty = new ValidatedMethod({
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Do work
|
||||
softRemove({ _id, collection: CreatureProperties });
|
||||
softRemove(CreatureProperties, property);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import '/imports/api/creature/creatures/methods/insertCreature';
|
||||
import '/imports/api/creature/creatures/methods/removeCreature';
|
||||
import '/imports/api/creature/creatures/methods/restCreature';
|
||||
import '/imports/api/creature/creatures/methods/updateCreature';
|
||||
import '/imports/api/creature/creatures/methods/changeAllowedLibraries';
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||
import { union } from 'lodash';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
const restCreature = new ValidatedMethod({
|
||||
name: 'creature.methods.rest',
|
||||
validate: new SimpleSchema({
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
restType: {
|
||||
type: String,
|
||||
allowedValues: ['shortRest', 'longRest'],
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ creatureId, restType }) {
|
||||
// Get action context
|
||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||
// Check permissions
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
|
||||
// Join, sort, and apply before triggers
|
||||
const beforeTriggers = union(
|
||||
actionContext.triggers.anyRest?.before, actionContext.triggers[restType]?.before
|
||||
).sort((a, b) => a.order - b.order);
|
||||
applyTriggers(beforeTriggers, null, actionContext);
|
||||
|
||||
// Rest
|
||||
actionContext.addLog({
|
||||
name: restType === 'shortRest' ? 'Short rest' : 'Long rest',
|
||||
});
|
||||
doRestWork(restType, actionContext);
|
||||
|
||||
// Join, sort, and apply after triggers
|
||||
const afterTriggers = union(
|
||||
actionContext.triggers.anyRest?.after, actionContext.triggers[restType]?.after
|
||||
).sort((a, b) => a.order - b.order);
|
||||
applyTriggers(afterTriggers, null, actionContext);
|
||||
|
||||
// Insert log
|
||||
actionContext.writeLog();
|
||||
},
|
||||
});
|
||||
|
||||
function doRestWork(restType, actionContext) {
|
||||
const creatureId = actionContext.creature._id;
|
||||
// Long rests reset short rest properties as well
|
||||
let resetFilter;
|
||||
if (restType === 'shortRest') {
|
||||
resetFilter = 'shortRest'
|
||||
} else {
|
||||
resetFilter = { $in: ['shortRest', 'longRest'] }
|
||||
}
|
||||
resetProperties(creatureId, resetFilter, actionContext);
|
||||
|
||||
// Reset half hit dice on a long rest, starting with the highest dice
|
||||
if (restType === 'longRest') {
|
||||
resetHitDice(creatureId, actionContext);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetProperties(creatureId, resetFilter, actionContext) {
|
||||
// Only apply to active properties
|
||||
const filter = {
|
||||
...getFilter.descendantsOfRoot(creatureId),
|
||||
reset: resetFilter,
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
};
|
||||
// update all attribute's damage
|
||||
const attributeFilter = {
|
||||
...filter,
|
||||
type: 'attribute',
|
||||
damage: { $nin: [0, undefined] },
|
||||
}
|
||||
CreatureProperties.find(attributeFilter).forEach(prop => {
|
||||
damagePropertyWork({
|
||||
prop,
|
||||
operation: 'increment',
|
||||
value: -prop.damage ?? 0,
|
||||
actionContext,
|
||||
logFunction(increment) {
|
||||
actionContext.addLog({
|
||||
name: prop.name,
|
||||
value: increment < 0 ? `Restored ${-increment}` : `Removed ${-increment}`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
// Update all action-like properties' usesUsed
|
||||
const actionFilter = {
|
||||
...filter,
|
||||
type: {
|
||||
$in: ['action', 'spell']
|
||||
},
|
||||
usesUsed: { $nin: [0, undefined] },
|
||||
};
|
||||
CreatureProperties.find(actionFilter, {
|
||||
fields: { name: 1, usesUsed: 1 }
|
||||
}).forEach(prop => {
|
||||
actionContext.addLog({
|
||||
name: prop.name,
|
||||
value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses`
|
||||
});
|
||||
});
|
||||
CreatureProperties.update(actionFilter, {
|
||||
$set: {
|
||||
usesUsed: 0,
|
||||
dirty: true,
|
||||
}
|
||||
}, {
|
||||
selector: { type: 'action' },
|
||||
multi: true,
|
||||
});
|
||||
}
|
||||
|
||||
function resetHitDice(creatureId, actionContext) {
|
||||
let hitDice = CreatureProperties.find({
|
||||
...getFilter.descendantsOfRoot(creatureId),
|
||||
type: 'attribute',
|
||||
attributeType: 'hitDice',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}).fetch();
|
||||
// Use a collator to do sorting in natural order
|
||||
let collator = new Intl.Collator('en', {
|
||||
numeric: true, sensitivity: 'base'
|
||||
});
|
||||
// Get the hit dice in decending order of hitDiceSize
|
||||
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
|
||||
hitDice.sort(compare);
|
||||
// Get the total number of hit dice that can be recovered this rest
|
||||
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0);
|
||||
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
|
||||
let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1);
|
||||
// recover each hit dice in turn until the recoverable amount is used up
|
||||
let amountToRecover;
|
||||
hitDice.forEach(hd => {
|
||||
if (!recoverableHd) return;
|
||||
amountToRecover = Math.min(recoverableHd, hd.damage ?? 0);
|
||||
if (!amountToRecover) return;
|
||||
recoverableHd -= amountToRecover;
|
||||
damagePropertyWork({
|
||||
prop: hd,
|
||||
operation: 'increment',
|
||||
value: -amountToRecover,
|
||||
actionContext,
|
||||
logFunction(increment) {
|
||||
actionContext.addLog({
|
||||
name: hd.name,
|
||||
value: increment < 0 ? `Restored ${-increment} hit dice` : `Removed ${increment} hit dice`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default restCreature;
|
||||
@@ -103,7 +103,7 @@ const insertDoc = new ValidatedMethod({
|
||||
|
||||
doc.parentId = parentId;
|
||||
|
||||
const lastOrder = Docs.find({}, { sort: { order: -1 } }).fetch()[0]?.order || 0;
|
||||
const lastOrder = Docs.find({}, { sort: { left: -1 } }).fetch()[0]?.order || 0;
|
||||
doc.order = lastOrder + 1;
|
||||
doc.urlName = 'new-doc-' + (lastOrder + 1);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface EngineAction {
|
||||
_stepThrough?: boolean;
|
||||
_decisions?: any[],
|
||||
creatureId: string;
|
||||
rootPropId: string;
|
||||
rootPropId?: string;
|
||||
targetIds?: string[];
|
||||
results: TaskResult[];
|
||||
taskCount: number;
|
||||
|
||||
@@ -13,6 +13,7 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import { getNumberFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
|
||||
import applyResetTask from '/imports/api/engine/action/tasks/applyResetTask';
|
||||
|
||||
export default async function applyActionProperty(
|
||||
task: PropTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
@@ -72,7 +73,12 @@ export default async function applyActionProperty(
|
||||
await applyChildren(action, prop, targetIds, userInput);
|
||||
}
|
||||
if (prop.actionType === 'event' && prop.variableName) {
|
||||
resetProperties(action, prop, result, userInput);
|
||||
await applyResetTask({
|
||||
subtaskFn: 'reset',
|
||||
prop,
|
||||
eventName: prop.variableName,
|
||||
targetIds: [action.creatureId],
|
||||
}, action, result, userInput);
|
||||
}
|
||||
|
||||
// Finish
|
||||
@@ -244,46 +250,3 @@ function applyCrits(value, scope, resultPushScope) {
|
||||
}
|
||||
return { criticalHit, criticalMiss };
|
||||
}
|
||||
|
||||
async function resetProperties(action: EngineAction, prop: any, result: TaskResult, userInput: InputProvider) {
|
||||
const attributes = getPropertiesOfType(action.creatureId, 'attribute');
|
||||
for (const att of attributes) {
|
||||
if (att.removed || att.inactive) continue;
|
||||
if (att.reset !== prop.variableName) continue;
|
||||
if (!att.damage) continue;
|
||||
applyTask(action, {
|
||||
prop: att,
|
||||
targetIds: [action.creatureId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
title: getPropertyTitle(att),
|
||||
operation: 'increment',
|
||||
value: -att.damage ?? 0,
|
||||
targetProp: att,
|
||||
},
|
||||
}, userInput)
|
||||
}
|
||||
const actions = [
|
||||
...getPropertiesOfType(action.creatureId, 'action'),
|
||||
...getPropertiesOfType(action.creatureId, 'spell'),
|
||||
]
|
||||
for (const act of actions) {
|
||||
if (act.removed || act.inactive) continue;
|
||||
if (act.reset !== prop.variableName) continue;
|
||||
if (!act.usesUsed) continue;
|
||||
result.mutations.push({
|
||||
targetIds: [action.creatureId],
|
||||
updates: [{
|
||||
propId: act._id,
|
||||
set: { usesUsed: 0 },
|
||||
type: act.type,
|
||||
}],
|
||||
contents: [{
|
||||
name: getPropertyTitle(act),
|
||||
value: act.usesUsed >= 0 ? `Restored ${act.usesUsed} uses` : `Removed ${-act.usesUsed} uses`,
|
||||
inline: true,
|
||||
...prop.silent && { silenced: true },
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export default async function applyDamageProperty(
|
||||
|
||||
// roll the dice only and store that string
|
||||
recalculateCalculation(prop.amount, action, 'compile', inputProvider);
|
||||
const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context);
|
||||
const { result: rolled } = await resolve('roll', prop.amount.valueNode, scope, context, inputProvider);
|
||||
if (rolled.parseType !== 'constant') {
|
||||
logValue.push(toString(rolled));
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export default async function applyDamageProperty(
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context);
|
||||
const { result: reduced } = await resolve('reduce', rolled, scope, context, inputProvider);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
|
||||
// Store the result
|
||||
@@ -102,11 +102,11 @@ export default async function applyDamageProperty(
|
||||
recalculateCalculation(prop.save.damageFunction, action, 'compile', inputProvider);
|
||||
context.errors = [];
|
||||
const { result: saveDamageRolled } = await resolve(
|
||||
'roll', prop.save.damageFunction.valueNode, scope, context
|
||||
'roll', prop.save.damageFunction.valueNode, scope, context, inputProvider
|
||||
);
|
||||
saveRoll = toString(saveDamageRolled);
|
||||
const { result: saveDamageResult } = await resolve(
|
||||
'reduce', saveDamageRolled, scope, context
|
||||
'reduce', saveDamageRolled, scope, context, inputProvider
|
||||
);
|
||||
result.appendParserContextErrors(context, damageTargets);
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
|
||||
@@ -4,7 +4,6 @@ import InputProvider from '/imports/api/engine/action/functions/userInput/InputP
|
||||
import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
||||
import { applyUnresolvedEffects } from '/imports/api/engine/action/methods/doCheck';
|
||||
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import { getVariables } from '/imports/api/engine/loadCreatures';
|
||||
@@ -64,11 +63,8 @@ export default async function applySavingThrowProperty(
|
||||
applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
||||
}
|
||||
|
||||
let rollModifierText = numberToSignedString(save.value, true);
|
||||
let rollModifier = save.value
|
||||
const { effectBonus, effectString } = applyUnresolvedEffects(save, scope)
|
||||
rollModifierText += effectString;
|
||||
rollModifier += effectBonus;
|
||||
const rollModifierText = numberToSignedString(save.value, true);
|
||||
const rollModifier = save.value;
|
||||
|
||||
let value, resultPrefix;
|
||||
if (save.advantage === 1) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import recalculateCalculation from '/imports/api/engine/action/functions/recalcu
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import { hasAncestorRelationship } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default async function spendResources(
|
||||
action: EngineAction, prop, targetIds: string[], result: TaskResult, userInput
|
||||
@@ -69,8 +70,11 @@ export default async function spendResources(
|
||||
params: {
|
||||
value: quantity,
|
||||
item,
|
||||
// If the item is an ancestor or descendant of this prop, skip the item's children to avoid
|
||||
// an infinite loop
|
||||
skipChildren: hasAncestorRelationship(item, prop),
|
||||
},
|
||||
}, userInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ export type Advantage = 0 | 1 | -1;
|
||||
|
||||
export type CheckParams = {
|
||||
advantage: Advantage;
|
||||
skillVariableName: string;
|
||||
abilityVariableName: string;
|
||||
skillVariableName?: string;
|
||||
abilityVariableName?: string;
|
||||
dc: number | null;
|
||||
contest?: true;
|
||||
targetSkillVariableName?: string;
|
||||
|
||||
@@ -5,12 +5,14 @@ import { union } from 'lodash';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import bulkWrite from '/imports/api/engine/shared/bulkWrite';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
|
||||
export default async function writeActionResults(action: EngineAction) {
|
||||
if (!action._id) throw new Meteor.Error('type-error', 'Action does not have an _id');
|
||||
EngineActions.remove(action._id);
|
||||
const engineActionPromise = EngineActions.removeAsync(action._id);
|
||||
const creaturePropUpdates: any[] = [];
|
||||
const logContents: any[] = [];
|
||||
|
||||
// Collect all the updates and log content
|
||||
action.results.forEach(result => {
|
||||
result.mutations.forEach(mutation => {
|
||||
@@ -18,7 +20,7 @@ export default async function writeActionResults(action: EngineAction) {
|
||||
logContents.push(...mutationToLogUpdates(mutation));
|
||||
});
|
||||
});
|
||||
const allTargetIds = union(...logContents.map(c => c.targetIds));
|
||||
const allTargetIds: string[] = union(...logContents.map(c => c.targetIds));
|
||||
|
||||
// Write the log
|
||||
const logPromise = CreatureLogs.insertAsync({
|
||||
@@ -30,5 +32,14 @@ export default async function writeActionResults(action: EngineAction) {
|
||||
// Write the bulk updates
|
||||
const bulkWritePromise = bulkWrite(creaturePropUpdates, CreatureProperties);
|
||||
|
||||
return Promise.all([logPromise, bulkWritePromise]);
|
||||
// Mark the creatures as dirty
|
||||
const creaturePromise = Creatures.updateAsync({
|
||||
_id: { $in: [action.creatureId, ...allTargetIds] },
|
||||
}, {
|
||||
$set: { dirty: true },
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
|
||||
return Promise.all([engineActionPromise, logPromise, bulkWritePromise, creaturePromise]);
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||
import rollDice from '/imports/parser/rollDice';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
|
||||
// TODO Migrate this to the new action engine
|
||||
|
||||
const doCheck = new ValidatedMethod({
|
||||
name: 'creatureProperties.doCheck',
|
||||
validate: new SimpleSchema({
|
||||
propId: SimpleSchema.RegEx.Id,
|
||||
scope: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ propId, scope }) {
|
||||
console.warn('do check not implemented');
|
||||
return;
|
||||
const prop = CreatureProperties.findOne(propId);
|
||||
if (!prop) throw new Meteor.Error('not-found', 'The property was not found');
|
||||
const creatureId = prop.root.id;
|
||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||
Object.assign(actionContext.scope, scope);
|
||||
actionContext.scope[`#${prop.type}`] = prop;
|
||||
|
||||
// Check permissions
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
|
||||
// Do the check
|
||||
doCheckWork({ prop, actionContext });
|
||||
},
|
||||
});
|
||||
|
||||
export default doCheck;
|
||||
|
||||
export function doCheckWork({ prop, actionContext }) {
|
||||
|
||||
applyTriggers(actionContext.triggers.check?.before, prop, actionContext);
|
||||
rollCheck(prop, actionContext);
|
||||
applyTriggers(actionContext.triggers.check?.after, prop, actionContext);
|
||||
|
||||
// Insert the log
|
||||
actionContext.writeLog();
|
||||
}
|
||||
|
||||
function rollCheck(prop, actionContext) {
|
||||
const scope = actionContext.scope;
|
||||
// get the modifier for the roll
|
||||
let rollModifier;
|
||||
let logName = `${prop.name} check`;
|
||||
if (prop.type === 'skill') {
|
||||
rollModifier = prop.value;
|
||||
if (prop.skillType === 'save') {
|
||||
if (prop.name.match(/save/i)) {
|
||||
logName = prop.name;
|
||||
} else {
|
||||
logName = prop.name ? `${prop.name} save` : 'Saving Throw';
|
||||
}
|
||||
}
|
||||
} else if (prop.type === 'attribute') {
|
||||
if (prop.attributeType === 'ability') {
|
||||
rollModifier = prop.modifier;
|
||||
} else {
|
||||
rollModifier = prop.value;
|
||||
}
|
||||
} else {
|
||||
throw (`${prop.type} not supported for checks`);
|
||||
}
|
||||
|
||||
let rollModifierText = numberToSignedString(rollModifier, true);
|
||||
|
||||
const { effectBonus, effectString } = applyUnresolvedEffects(prop, actionContext)
|
||||
rollModifierText += effectString;
|
||||
rollModifier += effectBonus;
|
||||
|
||||
let value, values, resultPrefix;
|
||||
if (scope['~checkAdvantage']?.value === 1) {
|
||||
logName += ' (Advantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (scope['~checkAdvantage']?.value === -1) {
|
||||
logName += ' (Disadvantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else {
|
||||
values = rollDice(1, 20);
|
||||
value = values[0];
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||
}
|
||||
const result = (value + rollModifier) || 0;
|
||||
scope['~checkDiceRoll'] = { value };
|
||||
scope['~checkRoll'] = { value: result };
|
||||
scope['~checkModifier'] = { value: rollModifier };
|
||||
actionContext.addLog({
|
||||
name: logName,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO replace this with recalculating and then rolling/reducing the value node
|
||||
export function applyUnresolvedEffects(prop, actionContext) {
|
||||
let effectBonus = 0;
|
||||
let effectString = '';
|
||||
if (!prop.effectIds) {
|
||||
return { effectBonus, effectString };
|
||||
}
|
||||
prop.effectIds.forEach(id => {
|
||||
const effect = getSingleProperty(actionContext.creature._id, id);
|
||||
if (!effect.amount?.parseNode) return;
|
||||
if (effect.operation !== 'add') return;
|
||||
recalculateCalculation(effect.amount, actionContext, undefined, 'reduce');
|
||||
if (typeof effect.amount?.value !== 'number') return;
|
||||
effectBonus += effect.amount.value;
|
||||
effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}`
|
||||
});
|
||||
return { effectBonus, effectString };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CheckParams } from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
|
||||
type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask;
|
||||
type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask | ResetTask;
|
||||
|
||||
export default Task;
|
||||
|
||||
@@ -31,9 +31,17 @@ export type ItemAsAmmoTask = BaseTask & {
|
||||
params: {
|
||||
value: number;
|
||||
item: any;
|
||||
skipChildren: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type CheckTask = BaseTask & CheckParams & {
|
||||
subtaskFn: 'check';
|
||||
}
|
||||
|
||||
export type ResetTask = BaseTask & {
|
||||
subtaskFn: 'reset',
|
||||
eventName: string;
|
||||
// One and only one target
|
||||
targetIds: [string];
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ export default async function applyDamagePropTask(
|
||||
type: targetProp.type,
|
||||
}],
|
||||
contents: [{
|
||||
name: 'Attribute damaged',
|
||||
value: `${numberToSignedString(-value)} ${getPropertyTitle(targetProp)}`,
|
||||
name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored',
|
||||
value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`,
|
||||
inline: true,
|
||||
...prop.silent && { silenced: true },
|
||||
}]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import {
|
||||
applyDefaultAfterPropTasks, applyTriggers
|
||||
applyDefaultAfterPropTasks, applyAfterTasksSkipChildren, applyTriggers
|
||||
} from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||
import {
|
||||
getEffectiveActionScope
|
||||
@@ -31,7 +31,7 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
|
||||
};
|
||||
value = scope['~ammoConsumed']?.value || 0;
|
||||
|
||||
const itemChildren = await getPropertyChildren(action.creatureId, item);
|
||||
const itemChildren = task.params.skipChildren ? [] : await getPropertyChildren(action.creatureId, item);
|
||||
|
||||
// Do the quantity adjustment
|
||||
// Check if property has quantity
|
||||
@@ -53,5 +53,10 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
|
||||
});
|
||||
|
||||
await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput);
|
||||
return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
|
||||
|
||||
if (task.params.skipChildren) {
|
||||
return applyAfterTasksSkipChildren(action, item, task.targetIds, userInput);
|
||||
} else {
|
||||
return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
|
||||
}
|
||||
}
|
||||
172
app/imports/api/engine/action/tasks/applyResetTask.ts
Normal file
172
app/imports/api/engine/action/tasks/applyResetTask.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import { ResetTask } from '/imports/api/engine/action/tasks/Task';
|
||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import { getCreature, getPropertiesByFilter, getPropertiesOfType } from '/imports/api/engine/loadCreatures';
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
|
||||
export default async function applyResetTask(
|
||||
task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider
|
||||
): Promise<void> {
|
||||
// Event name must be defined
|
||||
if (!task.eventName) return;
|
||||
|
||||
// This task can only be applied to a single target
|
||||
if (task.targetIds.length !== 1) {
|
||||
throw new Meteor.Error('wrong-number-of-targets', `Must reset the properties of a single creature at a time, ${task.targetIds.length} targets were provided`)
|
||||
}
|
||||
|
||||
// Print a title for the event
|
||||
let name: string;
|
||||
switch (task.eventName) {
|
||||
case 'shortRest':
|
||||
name = 'Short Rest';
|
||||
break;
|
||||
case 'longRest':
|
||||
name = 'Long Rest';
|
||||
break;
|
||||
default:
|
||||
name = task.eventName;
|
||||
break;
|
||||
}
|
||||
result.appendLog({ name }, task.targetIds);
|
||||
|
||||
// Reset the properties by this event name
|
||||
await resetProperties(task, action, result, userInput);
|
||||
|
||||
// Reset hit dice on a long rest, starting with the highest dice
|
||||
if (task.eventName === 'longRest') {
|
||||
await resetHitDice(task, action, result, userInput);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetProperties(task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider) {
|
||||
const creatureId = task.targetIds[0];
|
||||
|
||||
// Long rests reset short rest properties as well
|
||||
let mongoFilter: Mongo.Selector<object>
|
||||
if (task.eventName === 'longRest') {
|
||||
mongoFilter = { reset: { $in: ['shortRest', 'longRest'] } }
|
||||
} else {
|
||||
mongoFilter = { reset: task.eventName };
|
||||
}
|
||||
|
||||
const filterFn = (prop) => {
|
||||
if (task.eventName === 'longRest') {
|
||||
if (prop.reset !== 'longRest' && prop.reset !== 'shortRest') return false;
|
||||
} else {
|
||||
if (prop.reset !== task.eventName) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attributes
|
||||
|
||||
const attributeFilter: Mongo.Selector<object> = {
|
||||
...mongoFilter,
|
||||
type: 'attribute',
|
||||
damage: { $nin: [0, undefined] },
|
||||
}
|
||||
|
||||
const attributeFilterFunction = (att) => {
|
||||
if (att.type !== 'attribute') return false;
|
||||
if (!filterFn(att)) return false;
|
||||
if (att.damage === 0 || att.damage === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const attributes = getPropertiesByFilter(creatureId, attributeFilterFunction, attributeFilter);
|
||||
|
||||
for (const prop of attributes) {
|
||||
await applyTask(action, {
|
||||
prop: task.prop || prop,
|
||||
targetIds: [action.creatureId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
title: getPropertyTitle(prop),
|
||||
operation: 'increment',
|
||||
value: -prop.damage ?? 0,
|
||||
targetProp: prop,
|
||||
},
|
||||
}, userInput);
|
||||
}
|
||||
|
||||
// Action-like properties
|
||||
|
||||
const actionFilter = {
|
||||
...mongoFilter,
|
||||
type: {
|
||||
$in: ['action', 'spell']
|
||||
},
|
||||
usesUsed: { $nin: [0, undefined] },
|
||||
};
|
||||
|
||||
const actionFilterFunction = (prop) => {
|
||||
if (prop.type !== 'action' && prop.type !== 'spell') return false;
|
||||
if (!filterFn(prop)) return false;
|
||||
if (prop.usesUsed === 0 || prop.usesUsed === undefined) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const actionProps = getPropertiesByFilter(creatureId, actionFilterFunction, actionFilter);
|
||||
|
||||
for (const prop of actionProps) {
|
||||
result.mutations.push({
|
||||
targetIds: [creatureId],
|
||||
updates: [{
|
||||
propId: prop._id,
|
||||
type: prop.type,
|
||||
set: { usesUsed: 0 },
|
||||
}],
|
||||
contents: [{
|
||||
name: prop.name,
|
||||
value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses`
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function resetHitDice(task: ResetTask, action: EngineAction, result: TaskResult, userInput: InputProvider) {
|
||||
const creatureId = task.targetIds[0];
|
||||
|
||||
const hitDice = getPropertiesOfType(creatureId, 'hitDice');
|
||||
|
||||
// Use a collator to do sorting in natural order
|
||||
const collator = new Intl.Collator('en', {
|
||||
numeric: true, sensitivity: 'base'
|
||||
});
|
||||
|
||||
// Get the hit dice in decending order of hitDiceSize
|
||||
const compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
|
||||
hitDice.sort(compare);
|
||||
|
||||
// Get the total number of hit dice that can be recovered this rest
|
||||
const totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0);
|
||||
const creature = getCreature(creatureId);
|
||||
const resetMultiplier = creature.settings.hitDiceResetMultiplier || 0.5;
|
||||
let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1);
|
||||
|
||||
// recover each hit dice in turn until the recoverable amount is used up
|
||||
let amountToRecover;
|
||||
for (const hd of hitDice) {
|
||||
if (!recoverableHd) return;
|
||||
amountToRecover = Math.min(recoverableHd, hd.damage ?? 0);
|
||||
if (!amountToRecover) return;
|
||||
recoverableHd -= amountToRecover;
|
||||
|
||||
// Apply the damage prop task
|
||||
await applyTask(action, {
|
||||
prop: task.prop || hd,
|
||||
targetIds: [creatureId],
|
||||
subtaskFn: 'damageProp',
|
||||
params: {
|
||||
title: getPropertyTitle(hd),
|
||||
operation: 'increment',
|
||||
value: -amountToRecover,
|
||||
targetProp: hd,
|
||||
},
|
||||
}, userInput);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { getSingleProperty } from '/imports/api/engine/loadCreatures';
|
||||
import applyProperties from '/imports/api/engine/action/applyProperties';
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import applyCheckTask from '/imports/api/engine/action/tasks/applyCheckTask';
|
||||
import applyResetTask from '/imports/api/engine/action/tasks/applyResetTask';
|
||||
|
||||
// DamagePropTask promises a number of actual damage done
|
||||
export default async function applyTask(
|
||||
@@ -45,6 +46,10 @@ export default async function applyTask(
|
||||
return applyItemAsAmmoTask(task, action, result, inputProvider);
|
||||
case 'check':
|
||||
return applyCheckTask(task, action, result, inputProvider);
|
||||
case 'reset':
|
||||
return applyResetTask(task, action, result, inputProvider);
|
||||
default:
|
||||
throw 'No case defined for the given subtaskFn';
|
||||
}
|
||||
} else {
|
||||
// Get property
|
||||
|
||||
@@ -94,6 +94,36 @@ export function getPropertiesOfType(creatureId, propType) {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the properties of a creature that matches the filters given
|
||||
* @param creatureId The id of the creature
|
||||
* @param filterFn A function that returns true if the given prop matches the filter
|
||||
* @param mongoFilter A mongo selector that is exactly equal to the above function
|
||||
*/
|
||||
export function getPropertiesByFilter(creatureId, filterFn: (any) => boolean, mongoFilter: Mongo.Selector<object>) {
|
||||
const creature = loadedCreatures.get(creatureId);
|
||||
if (creature) {
|
||||
const props: CreatureProperty[] = []
|
||||
for (const prop of creature.properties.values()) {
|
||||
if (filterFn(prop)) {
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
props.sort((a, b) => a.left - b.left);
|
||||
return EJSON.clone(props);
|
||||
}
|
||||
// console.time(`Cache miss on creature properties: ${creatureId}`)
|
||||
const props = CreatureProperties.find({
|
||||
'root.id': creatureId,
|
||||
'removed': { $ne: true },
|
||||
...mongoFilter
|
||||
}, {
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||
return props;
|
||||
}
|
||||
|
||||
export function getCreature(creatureId: string) {
|
||||
const loadedCreature = loadedCreatures.get(creatureId);
|
||||
const loadedCreatureDoc = loadedCreature?.creature;
|
||||
@@ -207,7 +237,7 @@ export function getPropertyChildren(creatureId, property) {
|
||||
'parentId': property._id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
}
|
||||
}
|
||||
@@ -238,7 +268,7 @@ class LoadedCreature {
|
||||
'root.id': creatureId,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).observeChanges({
|
||||
added(id, fields) {
|
||||
fields._id = id;
|
||||
|
||||
@@ -284,7 +284,7 @@ const softRemoveLibraryNode = new ValidatedMethod({
|
||||
run({ _id }) {
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
softRemove({ _id, collection: LibraryNodes });
|
||||
softRemove(LibraryNodes, node);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -303,7 +303,7 @@ const restoreLibraryNode = new ValidatedMethod({
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
// Do work
|
||||
restore(LibraryNodes, _id);
|
||||
restore(LibraryNodes, node);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ const copyLibraryNodeTo = new ValidatedMethod({
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
|
||||
if (decendants.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||
|
||||
@@ -47,7 +47,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
|
||||
if (nodes.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
export function getCollectionByName(name: string): Mongo.Collection<TreeDoc> {
|
||||
const collection = Mongo.Collection.get(name)
|
||||
const collection: Mongo.Collection<TreeDoc> = Mongo.Collection.get(name)
|
||||
if (!collection) {
|
||||
throw new Meteor.Error('bad-collection-reference',
|
||||
`Parent references collection ${name}, which does not exist`
|
||||
@@ -93,16 +93,16 @@ type FilteredDoc = {
|
||||
export function filterToForest(
|
||||
collection: Mongo.Collection<TreeDoc>,
|
||||
rootId: string,
|
||||
filter?: Mongo.Query<TreeDoc>,
|
||||
filter?: Mongo.Selector<TreeDoc>,
|
||||
{
|
||||
options = <Mongo.Options<object>>{},
|
||||
options = <Mongo.Options<TreeDoc>>{},
|
||||
includeFilteredDocAncestors = false,
|
||||
includeFilteredDocDescendants = false
|
||||
} = {}
|
||||
): TreeNode<FilteredDoc>[] {
|
||||
if (!Meteor.isClient) throw 'Only available on the client';
|
||||
// Setup the filter
|
||||
let collectionFilter: Mongo.Query<TreeDoc> = {
|
||||
let collectionFilter: Mongo.Selector<TreeDoc> = {
|
||||
'root.id': rootId,
|
||||
'removed': { $ne: true },
|
||||
};
|
||||
@@ -113,16 +113,17 @@ export function filterToForest(
|
||||
}
|
||||
}
|
||||
// Set up the options
|
||||
let collectionSort = {
|
||||
let collectionSort: Mongo.Options<TreeDoc>['sort'] = {
|
||||
left: 1
|
||||
};
|
||||
if (options && options.sort) {
|
||||
if (options.sort) {
|
||||
collectionSort = {
|
||||
...collectionSort,
|
||||
// @ts-expect-error go home typescript you're drunk
|
||||
...options.sort,
|
||||
}
|
||||
}
|
||||
let collectionOptions: Mongo.Options<object> = {
|
||||
let collectionOptions: Mongo.Options<TreeDoc> = {
|
||||
sort: collectionSort,
|
||||
}
|
||||
if (options) {
|
||||
@@ -671,8 +672,8 @@ export function setDocToLastOrder(collection: Mongo.Collection<TreeDoc>, doc: Tr
|
||||
doc.left = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
export async function rebuildNestedSets(collection: Mongo.Collection<TreeDoc>, rootId: string) {
|
||||
const docs = await collection.find({
|
||||
export function rebuildNestedSets(collection: Mongo.Collection<TreeDoc>, rootId: string) {
|
||||
const docs = collection.find({
|
||||
'root.id': rootId,
|
||||
removed: { $ne: true }
|
||||
}, {
|
||||
@@ -681,13 +682,13 @@ export async function rebuildNestedSets(collection: Mongo.Collection<TreeDoc>, r
|
||||
//Reverse sorting so that arrays can be used as stacks with the first item on top
|
||||
left: 1,
|
||||
},
|
||||
}).fetchAsync();
|
||||
}).fetch();
|
||||
|
||||
const operations = calculateNestedSetOperations(docs);
|
||||
return writeBulkOperations(collection, operations);
|
||||
}
|
||||
|
||||
export async function rebuildCreatureNestedSets(creatureId) {
|
||||
export function rebuildCreatureNestedSets(creatureId) {
|
||||
const docs = getProperties(creatureId);
|
||||
const operations = calculateNestedSetOperations(docs);
|
||||
return writeBulkOperations(CreatureProperties as Mongo.Collection<TreeDoc, TreeDoc>, operations);
|
||||
@@ -823,8 +824,9 @@ export function applyNestedSetProperties<T extends TreeDoc>(docs: T[]): Forest<T
|
||||
* @param operations An array of bulk operations to write
|
||||
* @returns Promise<undefined>
|
||||
*/
|
||||
async function writeBulkOperations(collection: Mongo.Collection<TreeDoc>, operations) {
|
||||
if (Meteor.isServer && operations.length) {
|
||||
function writeBulkOperations(collection: Mongo.Collection<TreeDoc>, operations) {
|
||||
if (Meteor.isServer) {
|
||||
if (!operations.length) return Promise.resolve();
|
||||
return new Promise((resolve, reject) => {
|
||||
collection.rawCollection().bulkWrite(
|
||||
operations,
|
||||
@@ -841,20 +843,19 @@ async function writeBulkOperations(collection: Mongo.Collection<TreeDoc>, operat
|
||||
} else {
|
||||
// Don't do latency compensation if there are too many operations, it just causes client
|
||||
// lag without much benefit
|
||||
const promises = operations.map(op => {
|
||||
operations.forEach(op => {
|
||||
if (op.updateOne) {
|
||||
return collection.updateAsync(
|
||||
collection.update(
|
||||
op.updateOne.filter,
|
||||
op.updateOne.update,
|
||||
);
|
||||
} else if (op.updateMany) {
|
||||
return collection.updateAsync(
|
||||
collection.update(
|
||||
op.updateMany.filter,
|
||||
op.updateMany.update,
|
||||
{ multi: true },
|
||||
)
|
||||
}
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
import { getCollectionByName, getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
import { TreeDoc } from '/imports/api/parenting/ChildSchema';
|
||||
|
||||
export async function softRemove(collection: Mongo.Collection<TreeDoc> | string, doc?: TreeDoc | string) {
|
||||
export function softRemove(collectionOrName: Mongo.Collection<TreeDoc> | string, docOrId?: TreeDoc | string) {
|
||||
const removalDate = new Date();
|
||||
if (typeof collection === 'string') {
|
||||
collection = getCollectionByName(collection);
|
||||
|
||||
let collection: Mongo.Collection<TreeDoc>;
|
||||
if (typeof collectionOrName === 'string') {
|
||||
collection = getCollectionByName(collectionOrName);
|
||||
} else {
|
||||
collection = collectionOrName;
|
||||
}
|
||||
if (typeof doc === 'string') {
|
||||
doc = await collection.findOneAsync(doc);
|
||||
|
||||
let doc: TreeDoc | undefined;
|
||||
if (typeof docOrId === 'string') {
|
||||
doc = collection.findOne(docOrId);
|
||||
} else {
|
||||
doc = docOrId
|
||||
}
|
||||
if (!doc) {
|
||||
throw new Meteor.Error('not found', 'The document to remove was not found');
|
||||
}
|
||||
|
||||
// Remove this document
|
||||
const removeDocPromise = collection.updateAsync(
|
||||
collection.update(
|
||||
doc._id,
|
||||
{
|
||||
$set: {
|
||||
@@ -23,13 +32,11 @@ export async function softRemove(collection: Mongo.Collection<TreeDoc> | string,
|
||||
$unset: {
|
||||
removedWith: 1,
|
||||
}
|
||||
}, {
|
||||
selector: { type: 'any' },
|
||||
},
|
||||
}
|
||||
);
|
||||
// Remove all the descendants that have not yet been removed, and set them to be
|
||||
// removed with this document
|
||||
const removeDescendantsPromise = collection.updateAsync({
|
||||
collection.update({
|
||||
...getFilter.descendants(doc),
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
@@ -39,10 +46,8 @@ export async function softRemove(collection: Mongo.Collection<TreeDoc> | string,
|
||||
removedWith: doc._id,
|
||||
}
|
||||
}, {
|
||||
selector: { type: 'any' },
|
||||
multi: true,
|
||||
});
|
||||
return Promise.all([removeDocPromise, removeDescendantsPromise]);
|
||||
}
|
||||
|
||||
const restoreError = function () {
|
||||
@@ -51,18 +56,26 @@ const restoreError = function () {
|
||||
);
|
||||
};
|
||||
|
||||
export async function restore(collection: Mongo.Collection<TreeDoc> | string, doc: TreeDoc | string, extraUpdates?) {
|
||||
if (typeof collection === 'string') {
|
||||
collection = getCollectionByName(collection);
|
||||
export function restore(collectionOrName: Mongo.Collection<TreeDoc> | string, docOrId: TreeDoc | string, extraUpdates?) {
|
||||
|
||||
let collection: Mongo.Collection<TreeDoc>;
|
||||
if (typeof collectionOrName === 'string') {
|
||||
collection = getCollectionByName(collectionOrName);
|
||||
} else {
|
||||
collection = collectionOrName;
|
||||
}
|
||||
if (typeof doc === 'string') {
|
||||
const foundDoc = await collection.findOneAsync(doc)
|
||||
if (!foundDoc) {
|
||||
throw new Meteor.Error('not found', 'The document to remove was not found');
|
||||
}
|
||||
doc = foundDoc;
|
||||
|
||||
let doc: TreeDoc | undefined;
|
||||
if (typeof docOrId === 'string') {
|
||||
doc = collection.findOne(docOrId);
|
||||
} else {
|
||||
doc = docOrId
|
||||
}
|
||||
const numUpdated: number = await collection.updateAsync({
|
||||
if (!doc) {
|
||||
throw new Meteor.Error('not found', 'The document to remove was not found');
|
||||
}
|
||||
|
||||
const numUpdated: number = collection.update({
|
||||
_id: doc._id,
|
||||
removedWith: { $exists: false }
|
||||
}, {
|
||||
@@ -71,11 +84,11 @@ export async function restore(collection: Mongo.Collection<TreeDoc> | string, do
|
||||
removedAt: 1,
|
||||
},
|
||||
...extraUpdates
|
||||
}, {
|
||||
selector: { type: 'any' },
|
||||
});
|
||||
|
||||
if (numUpdated === 0) restoreError();
|
||||
return collection.updateAsync({
|
||||
|
||||
return collection.update({
|
||||
removedWith: doc._id,
|
||||
}, {
|
||||
$unset: {
|
||||
@@ -84,7 +97,6 @@ export async function restore(collection: Mongo.Collection<TreeDoc> | string, do
|
||||
removedWith: 1,
|
||||
}
|
||||
}, {
|
||||
selector: { type: 'any' },
|
||||
multi: true,
|
||||
});
|
||||
}) + 1;
|
||||
}
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<v-menu
|
||||
v-model="open"
|
||||
origin="center center"
|
||||
transition="scale-transition"
|
||||
nudge-left="100px"
|
||||
nudge-top="100px"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
v-bind="$attrs"
|
||||
:class="buttonClass"
|
||||
v-on="noClick ? {} : on"
|
||||
@click="e => { if (!noClick) e.stopPropagation(); }"
|
||||
>
|
||||
<slot />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-sheet class="d-flex flex-column align-center justify-center">
|
||||
<v-btn-toggle
|
||||
v-model="dataAdvantage"
|
||||
color="accent"
|
||||
>
|
||||
<v-btn :value="-1">
|
||||
Disadvantage
|
||||
</v-btn>
|
||||
<v-btn :value="1">
|
||||
Advantage
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<div class="ma-1 text-subtitle-2">
|
||||
{{ name }}
|
||||
</div>
|
||||
<div>
|
||||
<v-scale-transition
|
||||
origin="center center"
|
||||
>
|
||||
<vertical-hex
|
||||
v-if="dataAdvantage"
|
||||
style="position:absolute; transition: margin-left 0.3s ease;"
|
||||
:style="{marginLeft: dataAdvantage == 1 ? '24px' : '-24px'}"
|
||||
disable-hover
|
||||
/>
|
||||
</v-scale-transition>
|
||||
<vertical-hex @click="roll">
|
||||
<div>
|
||||
Roll
|
||||
</div>
|
||||
<div v-if="rollText">
|
||||
{{ rollText }}
|
||||
</div>
|
||||
</vertical-hex>
|
||||
</div>
|
||||
<v-btn
|
||||
text
|
||||
color="primary"
|
||||
style="align-self: end"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</v-sheet>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import VerticalHex from '/imports/client/ui/components/VerticalHex.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VerticalHex
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
rollText: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
buttonClass: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
advantage: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
noClick: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
open: false,
|
||||
dataAdvantage: this.advantage,
|
||||
}},
|
||||
watch: {
|
||||
advantage(val){
|
||||
this.dataAdvantage = val;
|
||||
},
|
||||
open(val){
|
||||
if(!val){
|
||||
this.dataAdvantage = this.advantage;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
roll(){
|
||||
this.$emit('roll', {advantage: this.dataAdvantage});
|
||||
this.open = false;
|
||||
},
|
||||
close(){
|
||||
this.open = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -1,99 +0,0 @@
|
||||
<template lang="html">
|
||||
<v-card>
|
||||
<template v-if="!result">
|
||||
<v-btn-toggle v-model="advantage">
|
||||
<v-btn text>
|
||||
Advantage
|
||||
</v-btn>
|
||||
<v-btn text>
|
||||
Disadvantage
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-card-text>
|
||||
<div class="layout justify-center align-center">
|
||||
<v-btn
|
||||
large
|
||||
fab
|
||||
outlined
|
||||
@click="makeRoll"
|
||||
>
|
||||
<div class="text-h4">
|
||||
{{ numberToSignedString(bonus) }}
|
||||
</div>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<div class="text-h6">
|
||||
<span
|
||||
v-for="(roll, index) of rolls"
|
||||
:key="index"
|
||||
class="roll"
|
||||
:class="{strikethrough: index !== chosenRollIndex}"
|
||||
>
|
||||
{{ roll }}
|
||||
</span>
|
||||
<span class="ml-1">
|
||||
{{ numberToSignedString(bonus) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-h4">
|
||||
{{ result }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import numberToSignedString from '../../../../api/utility/numberToSignedString';
|
||||
export default {
|
||||
props: {
|
||||
attributeVarName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
attributeName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bonus: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
advantage: undefined,
|
||||
result: undefined,
|
||||
rolls: undefined,
|
||||
chosenRoll: undefined,
|
||||
chosenRollIndex: undefined,
|
||||
}},
|
||||
methods: {
|
||||
makeRoll(){
|
||||
//let {rolls, bonus, chosenRoll, result} = doCheckWork.call();
|
||||
this.rolls = [12, 8];
|
||||
if (this.advantage === 1){
|
||||
this.chosenRoll = 8;
|
||||
} else {
|
||||
this.chosenRoll = 12;
|
||||
}
|
||||
this.result = this.chosenRoll + this.bonus;
|
||||
this.chosenRollIndex = this.rolls.indexOf(this.chosenRoll);
|
||||
},
|
||||
numberToSignedString,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@
|
||||
:loading="loading"
|
||||
:disabled="context.editPermission === false"
|
||||
outlined
|
||||
:data-id="`rest-btn-${type}`"
|
||||
style="width: 160px;"
|
||||
@click="rest"
|
||||
>
|
||||
@@ -14,7 +15,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import restCreature from '/imports/api/creature/creatures/methods/restCreature';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
@@ -36,19 +37,21 @@ export default {
|
||||
methods: {
|
||||
rest(){
|
||||
this.loading = true;
|
||||
restCreature.call({
|
||||
creatureId: this.creatureId,
|
||||
restType: this.type,
|
||||
}, error => {
|
||||
const emptyProp = {
|
||||
_id: this.creatureId,
|
||||
root: { id: this.creatureId },
|
||||
};
|
||||
doAction(emptyProp, this.$store, `rest-btn-${this.type}`, {
|
||||
subtaskFn: 'reset',
|
||||
prop: emptyProp,
|
||||
targetIds: [this.creatureId],
|
||||
eventName: this.type,
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
|
||||
@@ -1,73 +1,101 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<template slot="toolbar">
|
||||
<div class="d-flex flex-column">
|
||||
<v-toolbar
|
||||
class="base-dialog-toolbar"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
@click="cancel"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>
|
||||
Action
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<log-content :model="allLogContent" />
|
||||
<component
|
||||
:is="activeInput"
|
||||
v-if="activeInput"
|
||||
v-model="userInput"
|
||||
v-bind="activeInputParams"
|
||||
@continue="continueAction"
|
||||
@set-input-ready="setInputReady"
|
||||
/>
|
||||
<v-btn
|
||||
slot="actions"
|
||||
text
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-spacer slot="actions" />
|
||||
<v-btn
|
||||
v-show="!actionDone"
|
||||
slot="actions"
|
||||
text
|
||||
:disabled="!userInputReady || !resumeActionFn"
|
||||
@click="stepAction"
|
||||
>
|
||||
Step
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="actionDone"
|
||||
slot="actions"
|
||||
text
|
||||
@click="finishAction"
|
||||
>
|
||||
{{ 'Apply Results' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
slot="actions"
|
||||
text
|
||||
:disabled="actionBusy"
|
||||
@click="startAction"
|
||||
>
|
||||
{{ 'Start' }}
|
||||
</v-btn>
|
||||
</dialog-base>
|
||||
</v-toolbar>
|
||||
<div class="action-dialog-content">
|
||||
<div class="action-dialog-layout d-flex">
|
||||
<component
|
||||
:is="activeInput"
|
||||
v-if="activeInput"
|
||||
v-model="userInput"
|
||||
class="action-input"
|
||||
v-bind="activeInputParams"
|
||||
@continue="continueAction"
|
||||
@set-input-ready="setInputReady"
|
||||
/>
|
||||
<div
|
||||
class="log-preview card-raised-background d-flex flex-column align-end justify-end"
|
||||
style="flex-basis: 256px;"
|
||||
>
|
||||
<v-card
|
||||
v-if="allLogContent && allLogContent.length"
|
||||
class="ma-2 log-entry"
|
||||
>
|
||||
<v-card-text
|
||||
class="pa-2"
|
||||
>
|
||||
<log-content :model="allLogContent" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
text
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-spacer slot="actions" />
|
||||
<v-btn
|
||||
v-show="!actionDone"
|
||||
text
|
||||
:disabled="!userInputReady || !resumeActionFn"
|
||||
@click="stepAction"
|
||||
>
|
||||
Step
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="actionDone"
|
||||
text
|
||||
@click="finishAction"
|
||||
>
|
||||
{{ 'Apply Results' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
text
|
||||
:disabled="actionBusy"
|
||||
@click="startAction"
|
||||
>
|
||||
{{ 'Start' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
||||
import EngineActions from '/imports/api/engine/action/EngineActions';
|
||||
import applyAction from '/imports/api/engine/action/functions/applyAction';
|
||||
import getDeterministicDiceRoller from '/imports/api/engine/action/functions/userInput/getDeterministicDiceRoller';
|
||||
|
||||
import AdvantageInput from '/imports/client/ui/creature/actions/input/AdvantageInput.vue';
|
||||
import CheckInput from '/imports/client/ui/creature/actions/input/CheckInput.vue';
|
||||
import RollInput from '/imports/client/ui/creature/actions/input/RollInput.vue';
|
||||
import getDeterministicDiceRoller from '/imports/api/engine/action/functions/userInput/getDeterministicDiceRoller';
|
||||
import ChoiceInput from '/imports/client/ui/creature/actions/input/ChoiceInput.vue';
|
||||
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
||||
import EngineActions from '/imports/api/engine/action/EngineActions';
|
||||
import LogContent from '/imports/client/ui/log/LogContent.vue';
|
||||
import RollInput from '/imports/client/ui/creature/actions/input/RollInput.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
AdvantageInput,
|
||||
CheckInput,
|
||||
RollInput,
|
||||
ChoiceInput,
|
||||
DialogBase,
|
||||
LogContent,
|
||||
RollInput,
|
||||
},
|
||||
props: {
|
||||
actionId: {
|
||||
@@ -133,7 +161,11 @@ export default {
|
||||
applyAction(
|
||||
this.actionResult, this, { simulate: true, stepThrough, task: this.task}
|
||||
).then(() => {
|
||||
this.actionDone = true
|
||||
this.actionDone = true;
|
||||
// If we aren't stepping through close the dialog and apply the action
|
||||
if (!this.actionResult._stepThrough) {
|
||||
this.$store.dispatch('popDialogStack', this.actionResult);
|
||||
}
|
||||
});
|
||||
},
|
||||
stepAction() {
|
||||
@@ -172,17 +204,26 @@ export default {
|
||||
},
|
||||
// inputProvider methods
|
||||
async rollDice(dice) {
|
||||
return Promise.resolve(this.deterministicDiceRoller(dice));
|
||||
/* Dice Animation and user control goes here:
|
||||
this.activeInputParams = {
|
||||
deterministicDiceRoller: this.deterministicDiceRoller,
|
||||
dice
|
||||
};
|
||||
this.activeInput = 'roll-input';
|
||||
return this.promiseInput();
|
||||
*/
|
||||
},
|
||||
async nextStep(task) {
|
||||
return this.promiseInput();
|
||||
},
|
||||
async choose(choices, quantity) {
|
||||
this.userInput = [];
|
||||
this.activeInputParams = {
|
||||
choices,
|
||||
quantity
|
||||
};
|
||||
this.activeInput = 'choice-input'
|
||||
return this.promiseInput();
|
||||
},
|
||||
async advantage(suggestedAdvantage) {
|
||||
@@ -199,3 +240,45 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.base-dialog-toolbar {
|
||||
z-index: 2;
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.action-dialog-content {
|
||||
container-type: size;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.action-dialog-content, .action-dialog-layout {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.action-input {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-preview {
|
||||
flex-basis: 256px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.action-dialog-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
.action-input {
|
||||
height: unset;
|
||||
}
|
||||
.log-preview {
|
||||
flex-basis: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineAc
|
||||
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||
import applyAction from '/imports/api/engine/action/functions/applyAction';
|
||||
import { runAction } from '/imports/api/engine/action/methods/runAction';
|
||||
import getDeterministicDiceRoller from '/imports/api/engine/action/functions/userInput/getDeterministicDiceRoller';
|
||||
|
||||
/**
|
||||
* Apply an action on the client that first creates the action on both the client and server, then
|
||||
@@ -44,7 +45,7 @@ export default async function doAction(
|
||||
// Either way, call the action method afterwards
|
||||
try {
|
||||
const finishedAction = await applyAction(
|
||||
action, errorOnInputRequest, { simulate: true, task }
|
||||
action, getErrorOnInputRequestProvider(action._id), { simulate: true, task }
|
||||
);
|
||||
return callActionMethod(finishedAction, task);
|
||||
} catch (e) {
|
||||
@@ -76,10 +77,13 @@ const throwInputRequestedError = () => {
|
||||
throw 'input-requested';
|
||||
}
|
||||
|
||||
const errorOnInputRequest: InputProvider = {
|
||||
nextStep: throwInputRequestedError,
|
||||
rollDice: throwInputRequestedError,
|
||||
choose: throwInputRequestedError,
|
||||
advantage: throwInputRequestedError,
|
||||
check: throwInputRequestedError,
|
||||
function getErrorOnInputRequestProvider(actionId) {
|
||||
const errorOnInputRequest: InputProvider = {
|
||||
nextStep: throwInputRequestedError,
|
||||
rollDice: getDeterministicDiceRoller(actionId),
|
||||
choose: throwInputRequestedError,
|
||||
advantage: throwInputRequestedError,
|
||||
check: throwInputRequestedError,
|
||||
}
|
||||
return errorOnInputRequest;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ export default {
|
||||
methods: {
|
||||
emitInput(e) {
|
||||
e = e || 0;
|
||||
console.log(e);
|
||||
this.$emit('input', e)
|
||||
}
|
||||
}
|
||||
|
||||
87
app/imports/client/ui/creature/actions/input/ChoiceInput.vue
Normal file
87
app/imports/client/ui/creature/actions/input/ChoiceInput.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="choice-input">
|
||||
<v-expansion-panels
|
||||
accordion
|
||||
tile
|
||||
multiple
|
||||
hover
|
||||
>
|
||||
<v-expansion-panel
|
||||
v-for="prop in choices"
|
||||
:key="prop._id"
|
||||
:model="prop"
|
||||
:data-id="prop._id"
|
||||
>
|
||||
<v-expansion-panel-header>
|
||||
<template #default="{ open }">
|
||||
<v-checkbox
|
||||
v-model="selectedItems"
|
||||
class="my-0 py-0 mr-2 flex-grow-0"
|
||||
hide-details
|
||||
:value="prop._id"
|
||||
:disabled="!selectedItems.includes(prop._id) && selectedItems.length >= quantity.max"
|
||||
@click.stop
|
||||
/>
|
||||
<tree-node-view :model="prop" />
|
||||
<template v-if="open">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
icon
|
||||
class="flex-grow-0"
|
||||
@click.stop="openPropertyDetails(prop._id)"
|
||||
>
|
||||
<v-icon>mdi-window-restore</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content class="py-4">
|
||||
<property-viewer :model="prop" />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<v-btn
|
||||
:disabled="!canContinue"
|
||||
@click="$emit('continue');"
|
||||
>
|
||||
Done
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import PropertyViewer from '/imports/client/ui/properties/shared/PropertyViewer.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeView,
|
||||
PropertyViewer,
|
||||
},
|
||||
props: {
|
||||
choices: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
quantity: {
|
||||
type: Object,
|
||||
default: () => ({min: 0, max: 1}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedItems: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canContinue() {
|
||||
return this.selectedItems.length >= this.quantity.min;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedItems(val) {
|
||||
this.$emit('input', val)
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -155,7 +155,7 @@ export default {
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{owner: userId, archived: {$ne: true}},
|
||||
{sort: {order: 1}},
|
||||
{sort: {left: 1}},
|
||||
).map(folder => {
|
||||
folder.creatures = Creatures.find(
|
||||
{
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{owner: userId},
|
||||
{sort: {order: 1}},
|
||||
{sort: {left: 1}},
|
||||
).map(folder => {
|
||||
folder.creatures = ArchiveCreatureFiles.find(
|
||||
{
|
||||
|
||||
@@ -319,7 +319,7 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).fetch();
|
||||
},
|
||||
classLevels() {
|
||||
@@ -331,7 +331,7 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
});
|
||||
},
|
||||
slotBuildTree(){
|
||||
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -163,7 +163,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
},
|
||||
creature() {
|
||||
@@ -188,7 +188,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
carriedItems() {
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
equippedItems() {
|
||||
@@ -215,7 +215,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
equipmentParentRef() {
|
||||
|
||||
@@ -90,7 +90,7 @@ export default {
|
||||
...getFilter.descendantsOfRoot(this.creatureId),
|
||||
$nor: [getFilter.descendantsOfAll(allNotes)],
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
sort: {left: 1},
|
||||
});
|
||||
},
|
||||
creature(){
|
||||
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
spellLists() {
|
||||
@@ -168,7 +168,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
v-for="healthBar in properties.attribute.healthBar"
|
||||
:key="healthBar._id"
|
||||
:model="healthBar"
|
||||
@change="({ type, value }) => incrementChange(healthBar._id, { type, value: -value })"
|
||||
@change="({ type, value }) => incrementChange(healthBar._id, { type, value })"
|
||||
@click="clickProperty({_id: healthBar._id})"
|
||||
/>
|
||||
</v-card>
|
||||
@@ -392,7 +392,6 @@
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import HealthBar from '/imports/client/ui/properties/components/attributes/HealthBar.vue';
|
||||
import AttributeCard from '/imports/client/ui/properties/components/attributes/AttributeCard.vue';
|
||||
import AbilityListTile from '/imports/client/ui/properties/components/attributes/AbilityListTile.vue';
|
||||
@@ -412,6 +411,8 @@ import FolderGroupCard from '/imports/client/ui/properties/components/folders/Fo
|
||||
import { get, set, uniqBy } from 'lodash';
|
||||
import { docsToForest } from '/imports/api/parenting/parentingFunctions';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
|
||||
|
||||
function walkDown(forest, callback){
|
||||
let stack = [...forest];
|
||||
@@ -517,7 +518,6 @@ export default {
|
||||
return uniqBy(conditionals, '_id');
|
||||
},
|
||||
},
|
||||
// @ts-ignore Meteor isn't defined on vue
|
||||
meteor: {
|
||||
properties() {
|
||||
const creature = this.creature;
|
||||
@@ -557,7 +557,7 @@ export default {
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
}
|
||||
const allProps = CreatureProperties.find(filter, { sort: { order: -1 } }).fetch();
|
||||
const allProps = CreatureProperties.find(filter, { sort: { left: -1 } }).fetch();
|
||||
const forest = docsToForest(allProps);
|
||||
const properties = { folder: {}, attribute: {}, skill: {} };
|
||||
walkDown(forest, node => {
|
||||
@@ -593,7 +593,7 @@ export default {
|
||||
deactivatedByToggle: { $ne: true },
|
||||
showUI: true,
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -613,14 +613,24 @@ export default {
|
||||
});
|
||||
},
|
||||
incrementChange(_id, { type, value, ack }) {
|
||||
damageProperty.call({
|
||||
_id,
|
||||
operation: type,
|
||||
value: -value
|
||||
}, error => {
|
||||
const model = CreatureProperties.findOne(_id);
|
||||
if (type === 'increment') value = -value;
|
||||
doAction(model, this.$store, model._id, {
|
||||
subtaskFn: 'damageProp',
|
||||
prop: model,
|
||||
targetIds: [model.root.id],
|
||||
params: {
|
||||
title: getPropertyTitle(model),
|
||||
operation: type,
|
||||
value,
|
||||
targetProp: model,
|
||||
}
|
||||
}).then(() =>{
|
||||
ack?.();
|
||||
}).catch((error) => {
|
||||
if (ack) {
|
||||
ack(error);
|
||||
} else if (error) {
|
||||
} else {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}
|
||||
@@ -637,7 +647,3 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -212,7 +212,7 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).fetch();
|
||||
},
|
||||
classLevels() {
|
||||
@@ -224,7 +224,7 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
});
|
||||
},
|
||||
editPermission() {
|
||||
|
||||
@@ -111,7 +111,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
creature() {
|
||||
@@ -133,7 +133,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).map(c => {
|
||||
c.items = CreatureProperties.find({
|
||||
'parentId': c._id,
|
||||
@@ -143,7 +143,7 @@ export default {
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).fetch();
|
||||
return c;
|
||||
});
|
||||
@@ -158,7 +158,7 @@ export default {
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
equippedItems() {
|
||||
@@ -169,7 +169,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
equipmentParentRef() {
|
||||
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
}).fetch();
|
||||
},
|
||||
spellsWithoutList() {
|
||||
@@ -96,7 +96,7 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
}).map(sl => {
|
||||
sl.spells = CreatureProperties.find({
|
||||
...getFilter.descendants(sl),
|
||||
|
||||
@@ -363,7 +363,7 @@ import { uniqBy } from 'lodash';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
const getProperties = function (creature, filter, options = {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
}) {
|
||||
if (!creature) return;
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
@@ -434,7 +434,7 @@ export default {
|
||||
deactivatedByToggle: { $ne: true },
|
||||
showUI: true,
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
healthBars() {
|
||||
|
||||
@@ -175,7 +175,7 @@ export default {
|
||||
'ancestors.id': this.model._id,
|
||||
'removed': { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).map(prop => {
|
||||
// Get all the props we don't want to show the decendants of and
|
||||
// where they might appear in the ancestor list
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import pushToProperty from '/imports/api/creature/creatureProperties/methods/pushToProperty';
|
||||
import pullFromProperty from '/imports/api/creature/creatureProperties/methods/pullFromProperty';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty';
|
||||
@@ -94,6 +93,7 @@ import Breadcrumbs from '/imports/client/ui/creature/creatureProperties/Breadcru
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode';
|
||||
import PropertyViewer from '/imports/client/ui/properties/shared/PropertyViewer.vue';
|
||||
import copyPropertyToLibrary from '/imports/api/creature/creatureProperties/methods/copyPropertyToLibrary';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -177,7 +177,27 @@ export default {
|
||||
updateCreatureProperty.call({_id: this.currentId, path, value}, ack);
|
||||
},
|
||||
damage({operation, value, ack}){
|
||||
damageProperty.call({_id: this.currentId, operation, value}, ack);
|
||||
const model = this.model;
|
||||
doAction(model, this.$store, model._id, {
|
||||
subtaskFn: 'damageProp',
|
||||
prop: model,
|
||||
targetIds: [model.root.id],
|
||||
params: {
|
||||
title: getPropertyTitle(model),
|
||||
operation: operation,
|
||||
value,
|
||||
targetProp: model,
|
||||
}
|
||||
}).then(() =>{
|
||||
ack?.();
|
||||
}).catch((error) => {
|
||||
if (ack) {
|
||||
ack(error);
|
||||
} else {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
push({path, value, ack}){
|
||||
pushToProperty.call({_id: this.currentId, path, value}, ack);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<template lang="html">
|
||||
<tree-node-list
|
||||
v-if="model && model.root"
|
||||
:children="children"
|
||||
:group="group"
|
||||
:organize="organize"
|
||||
:start-expanded="expanded"
|
||||
:root="model.root"
|
||||
@selected="e => $emit('selected', e)"
|
||||
@move-within-root="moveWithinRoot"
|
||||
@move-between-roots="moveBetweenRoots"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { docsToForest, getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
|
||||
import { moveBetweenRoots, moveWithinRoot } from '/imports/api/parenting/organizeMethods';
|
||||
import { getCollectionByName } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
},
|
||||
props: {
|
||||
// The document for which we are finding children
|
||||
model: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
organize: Boolean,
|
||||
group: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
expanded: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
if (!this.model?.root) return [];
|
||||
const collection = getCollectionByName(this.collection);
|
||||
const docs = collection.find({
|
||||
removed: { $ne: true },
|
||||
...getFilter.descendants(this.model),
|
||||
}, {
|
||||
sort: { left: 1 }
|
||||
}).fetch();
|
||||
this.$emit('length', docs.length);
|
||||
|
||||
return docsToForest(docs);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moveWithinRoot({ doc, newPosition }) {
|
||||
moveWithinRoot.callAsync({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: this.collection,
|
||||
},
|
||||
newPosition,
|
||||
});
|
||||
},
|
||||
moveBetweenRoots({ doc, newPosition, newRootRef }) {
|
||||
moveBetweenRoots.callAsync({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: this.collection,
|
||||
},
|
||||
newPosition,
|
||||
newRootRef,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
return Session.get('editingDocs');
|
||||
},
|
||||
docs() {
|
||||
const docs = Docs.find({ removed: {$ne: true} }, { sort: {order: 1} }).fetch();
|
||||
const docs = Docs.find({ removed: {$ne: true} }, { sort: {left: 1} }).fetch();
|
||||
return docsToForest(docs);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{ owner: userId, archived: { $ne: true } },
|
||||
{ sort: { order: 1 } },
|
||||
{ sort: { left: 1 } },
|
||||
).map(folder => {
|
||||
folder.creatures = Creatures.find(
|
||||
{
|
||||
|
||||
@@ -190,7 +190,7 @@ export default {
|
||||
removed: true,
|
||||
removedWith: { $exists: false },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
isOwner() {
|
||||
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{ owner: userId, archived: { $ne: true } },
|
||||
{ sort: { order: 1 } },
|
||||
{ sort: { left: 1 } },
|
||||
).map(folder => {
|
||||
folder.creatures = Creatures.find(
|
||||
{
|
||||
|
||||
@@ -113,13 +113,13 @@ export default {
|
||||
'parent': undefined,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
return Docs.find({
|
||||
'parentId': this.doc._id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
})
|
||||
},
|
||||
siblingDocs() {
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
'parentId': this.doc.parent?.id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
editing() {
|
||||
|
||||
@@ -167,10 +167,11 @@
|
||||
style="width: 100%"
|
||||
class="pa-2 no-hover"
|
||||
>
|
||||
<creature-properties-tree
|
||||
<descendant-properties-tree
|
||||
style="width: 100%;"
|
||||
organize
|
||||
:root="{collection, id: model._id}"
|
||||
:model="model"
|
||||
:root="model.root"
|
||||
:collection="collection"
|
||||
@selected="e => $emit('select-sub-property', e)"
|
||||
/>
|
||||
@@ -225,7 +226,7 @@ import InlineComputationField from '/imports/client/ui/properties/forms/shared/I
|
||||
import FormSection, { FormSections } from '/imports/client/ui/properties/forms/shared/FormSection.vue';
|
||||
import propertyFormIndex from '/imports/client/ui/properties/forms/shared/propertyFormIndex';
|
||||
import IconColorMenu from '/imports/client/ui/properties/forms/shared/IconColorMenu.vue';
|
||||
import CreaturePropertiesTree from '/imports/client/ui/creature/creatureProperties/CreaturePropertiesTree.vue';
|
||||
import DescendantPropertiesTree from '/imports/client/ui/creature/creatureProperties/DescendantPropertiesTree.vue';
|
||||
import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue';
|
||||
import { getSuggestedChildren } from '/imports/constants/PROPERTIES';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES';
|
||||
@@ -243,7 +244,7 @@ export default {
|
||||
FormSection,
|
||||
FormSections,
|
||||
IconColorMenu,
|
||||
CreaturePropertiesTree,
|
||||
DescendantPropertiesTree,
|
||||
OutlinedInput,
|
||||
...propertyFormIndex,
|
||||
},
|
||||
|
||||
@@ -106,7 +106,6 @@ import ActionConditionView from '/imports/client/ui/properties/components/action
|
||||
import AttributeConsumedView from '/imports/client/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/client/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
|
||||
import RollPopup from '/imports/client/ui/components/RollPopup.vue';
|
||||
import MarkdownText from '/imports/client/ui/components/MarkdownText.vue';
|
||||
import CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||
import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
|
||||
@@ -121,7 +120,6 @@ export default {
|
||||
ItemConsumedView,
|
||||
MarkdownText,
|
||||
PropertyIcon,
|
||||
RollPopup,
|
||||
CardHighlight,
|
||||
TreeNodeList,
|
||||
},
|
||||
@@ -198,7 +196,7 @@ export default {
|
||||
'ancestors.id': this.model._id,
|
||||
'removed': { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).map(prop => {
|
||||
// Get all the props we don't want to show the decendants of and
|
||||
// where they might appear in the ancestor list
|
||||
@@ -225,7 +223,12 @@ export default {
|
||||
this.$emit('click', e);
|
||||
},
|
||||
doAction() {
|
||||
doAction(this.model, this.$store, this.model._id);
|
||||
this.doActionLoading = true;
|
||||
doAction(this.model, this.$store, this.model._id).catch((e) => {
|
||||
console.error(e);
|
||||
}).finally(() => {
|
||||
this.doActionLoading = false;
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
sort: {left: 1},
|
||||
fields: {equipped: false},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,16 +7,14 @@
|
||||
class="ma-0"
|
||||
style="min-width: 40px;"
|
||||
>
|
||||
<roll-popup
|
||||
button-class="mr-4 py-2"
|
||||
<v-btn
|
||||
class="mr-4 py-2"
|
||||
text
|
||||
height="82"
|
||||
:roll-text="numberToSignedString(model.modifier)"
|
||||
:name="model.name"
|
||||
:advantage="model.advantage"
|
||||
:data-id="`check-btn-${model._id}`"
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
@roll="check"
|
||||
@click.stop="check"
|
||||
>
|
||||
<div>
|
||||
<div class="text-h4 mod">
|
||||
@@ -40,7 +38,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</roll-popup>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-content>
|
||||
@@ -64,15 +62,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import doCheck from '/imports/api/engine/action/methods/doCheck';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import RollPopup from '/imports/client/ui/components/RollPopup.vue';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
@@ -96,19 +90,21 @@ export default {
|
||||
click(e) {
|
||||
this.$emit('click', e);
|
||||
},
|
||||
check({ advantage }) {
|
||||
check() {
|
||||
this.checkLoading = true;
|
||||
doCheck.call({
|
||||
propId: this.model._id,
|
||||
scope: {
|
||||
'~checkAdvantage': { value: advantage },
|
||||
},
|
||||
}, error => {
|
||||
doAction(this.model, this.$store, `check-btn-${this.model._id}`, {
|
||||
subtaskFn: 'check',
|
||||
prop: this.model,
|
||||
targetIds: [this.model.root.id],
|
||||
advantage: this.model.advantage,
|
||||
skillVariableName: undefined,
|
||||
abilityVariableName: this.model.variableName,
|
||||
dc: null,
|
||||
}).catch(error => {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}).finally(() => {
|
||||
this.checkLoading = false;
|
||||
if (error) {
|
||||
console.error(error);
|
||||
snackbar({ text: error.reason });
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -153,4 +149,3 @@ export default {
|
||||
min-width: 42px;
|
||||
}
|
||||
</style>
|
||||
../../../../../api/engine/action/methods/doCheck
|
||||
@@ -5,23 +5,20 @@
|
||||
@mouseover="$emit('mouseover')"
|
||||
@mouseleave="$emit('mouseleave')"
|
||||
>
|
||||
<roll-popup
|
||||
<v-btn
|
||||
v-if="model.attributeType === 'modifier' || model.type === 'skill'"
|
||||
button-class="px-0"
|
||||
class="px-0"
|
||||
text
|
||||
height="70"
|
||||
min-width="72"
|
||||
:roll-text="computedValue && computedValue.toString()"
|
||||
:name="model.name"
|
||||
:advantage="model.advantage"
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
@roll="check"
|
||||
@click.stop="check"
|
||||
>
|
||||
<v-card-title class="value text-h4 flex-shrink-0">
|
||||
{{ computedValue }}
|
||||
</v-card-title>
|
||||
</roll-popup>
|
||||
</v-btn>
|
||||
<v-card-title
|
||||
v-else
|
||||
class="value text-h4 flex-shrink-0"
|
||||
@@ -47,15 +44,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import RollPopup from '/imports/client/ui/components/RollPopup.vue';
|
||||
import doCheck from '/imports/api/engine/action/methods/doCheck.js';
|
||||
import {snackbar} from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
@@ -82,19 +75,21 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
signed: numberToSignedString,
|
||||
check({advantage}){
|
||||
check(){
|
||||
this.checkLoading = true;
|
||||
doCheck.call({
|
||||
propId: this.model._id,
|
||||
scope: {
|
||||
'~checkAdvantage': { value: advantage },
|
||||
},
|
||||
}, error => {
|
||||
doAction(this.model, this.$store, `check-btn-${this.model._id}`, {
|
||||
subtaskFn: 'check',
|
||||
prop: this.model,
|
||||
targetIds: [this.model.root.id],
|
||||
advantage: this.model.advantage,
|
||||
skillVariableName: this.model.variableName,
|
||||
abilityVariableName: this.model.ability,
|
||||
dc: null,
|
||||
}).catch(error => {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}).finally(() => {
|
||||
this.checkLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -106,4 +101,4 @@ export default {
|
||||
min-width: 72px;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>../../../../../api/engine/action/methods/doCheck
|
||||
</style>
|
||||
|
||||
@@ -151,8 +151,9 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editing = false;
|
||||
},
|
||||
changeIncrementMenu(e) {
|
||||
this.$emit('change', e);
|
||||
changeIncrementMenu({ type, value }) {
|
||||
if (type === 'increment') value = -value;
|
||||
this.$emit('change', { type, value });
|
||||
this.editing = false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<template lang="html">
|
||||
<v-card class="pa-2">
|
||||
<health-bar
|
||||
v-for="attribute in attributes"
|
||||
:key="attribute._id"
|
||||
:model="attribute"
|
||||
@change="e => $emit('change', {_id: attribute._id, change: e})"
|
||||
@click="e => $emit('click', {_id: attribute._id})"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import HealthBar from '/imports/client/ui/properties/components/attributes/HealthBar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HealthBar,
|
||||
},
|
||||
props: {
|
||||
attributes: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="attributes.length"
|
||||
class="px-2 pt-2"
|
||||
>
|
||||
<health-bar-card
|
||||
:attributes="attributes"
|
||||
@change="healthBarChanged"
|
||||
@click="healthBarClicked"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
|
||||
import HealthBarCard from '/imports/client/ui/properties/components/attributes/HealthBarCard.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HealthBarCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||
},
|
||||
attributes() {
|
||||
let creature = this.creature;
|
||||
if (!creature) return;
|
||||
let filter = {
|
||||
'ancestors.id': creature._id,
|
||||
type: 'attribute',
|
||||
attributeType: 'healthBar',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
overridden: { $ne: true },
|
||||
$nor: [
|
||||
{ hideWhenTotalZero: true, total: 0 },
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
};
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
}
|
||||
return CreatureProperties.find(filter, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
healthBarClicked({ _id }) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
healthBarChanged({ _id, change }) {
|
||||
damageProperty.call({
|
||||
_id,
|
||||
operation: change.type,
|
||||
value: change.value
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -64,9 +64,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
@@ -97,14 +98,24 @@ export default {
|
||||
// return true
|
||||
return false;
|
||||
},
|
||||
damageProperty({type, value, ack}) {
|
||||
damageProperty.call({
|
||||
_id: this.model._id,
|
||||
operation: type,
|
||||
value: value
|
||||
}, error => {
|
||||
if (ack) ack(error);
|
||||
if (error) {
|
||||
damageProperty({ type, value, ack }) {
|
||||
const model = this.model;
|
||||
doAction(model, this.$store, model._id, {
|
||||
subtaskFn: 'damageProp',
|
||||
prop: model,
|
||||
targetIds: [model.root.id],
|
||||
params: {
|
||||
title: getPropertyTitle(model),
|
||||
operation: type,
|
||||
value,
|
||||
targetProp: model,
|
||||
}
|
||||
}).then(() =>{
|
||||
ack?.();
|
||||
}).catch((error) => {
|
||||
if (ack) {
|
||||
ack(error);
|
||||
} else {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).forEach(prop => {
|
||||
if (propComponents[prop.type]) {
|
||||
props.push(prop);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
v-else-if="model.attributeType === 'hitDice'"
|
||||
:model="model"
|
||||
@click="$emit('click')"
|
||||
@change="({ type, value }) => damageProperty({type, value: -value})"
|
||||
@change="damageProperty"
|
||||
/>
|
||||
<health-bar
|
||||
v-else-if="model.attributeType === 'healthBar'"
|
||||
@@ -30,7 +30,7 @@
|
||||
v-else-if="model.attributeType === 'resource'"
|
||||
:model="model"
|
||||
@click="$emit('click')"
|
||||
@change="({ type, value, ack }) => damageProperty({type, value: -value, ack})"
|
||||
@change="damageProperty"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
/>
|
||||
@@ -62,8 +62,9 @@ import ResourceCardContent from '/imports/client/ui/properties/components/attrib
|
||||
import AttributeCardContent from '/imports/client/ui/properties/components/attributes/AttributeCardContent.vue';
|
||||
import CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||
import FolderGroupChildren from '/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue';
|
||||
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -91,20 +92,30 @@ export default {
|
||||
hover: false,
|
||||
}},
|
||||
methods: {
|
||||
damageProperty(change) {
|
||||
damageProperty.call({
|
||||
_id: this.model._id,
|
||||
operation: change.type,
|
||||
value: change.value
|
||||
}, e => {
|
||||
console.log(change);
|
||||
change.ack?.(e);
|
||||
damageProperty({value, type, ack}) {
|
||||
const model = this.model;
|
||||
if (type === 'increment') value = -value;
|
||||
doAction(model, this.$store, model._id, {
|
||||
subtaskFn: 'damageProp',
|
||||
prop: model,
|
||||
targetIds: [model.root.id],
|
||||
params: {
|
||||
title: getPropertyTitle(model),
|
||||
operation: type,
|
||||
value,
|
||||
targetProp: model,
|
||||
}
|
||||
}).then(() =>{
|
||||
ack?.();
|
||||
}).catch((error) => {
|
||||
if (ack) {
|
||||
ack(error);
|
||||
} else {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
log({_id}) {
|
||||
console.log(...arguments)
|
||||
this.$emit('click-property', { _id });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
],
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}).forEach(prop => {
|
||||
if (propComponents[prop.type]) {
|
||||
props.push(prop);
|
||||
|
||||
@@ -48,14 +48,14 @@ export default {
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
const slotIds = slots.map(s => s._id);
|
||||
const slotChildren = CreatureProperties.find({
|
||||
'parentId': { $in: slotIds },
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
const tree = nodeArrayToTree([
|
||||
...slots.fetch(),
|
||||
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
v-if="!hideModifier"
|
||||
text
|
||||
tile
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
:data-id="`check-btn-${model._id}`"
|
||||
class="pl-3 pr-2 prof-mod mr-1 flex-shrink-0"
|
||||
@@ -58,6 +59,7 @@
|
||||
import ProficiencyIcon from '/imports/client/ui/properties/shared/ProficiencyIcon.vue';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -101,6 +103,7 @@ export default {
|
||||
this.$emit('click', e);
|
||||
},
|
||||
check() {
|
||||
this.checkLoading = true;
|
||||
doAction(this.model, this.$store, `check-btn-${this.model._id}`, {
|
||||
subtaskFn: 'check',
|
||||
prop: this.model,
|
||||
@@ -109,9 +112,12 @@ export default {
|
||||
skillVariableName: this.model.variableName,
|
||||
abilityVariableName: this.model.ability,
|
||||
dc: null,
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
})
|
||||
}).catch(error => {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}).finally(() => {
|
||||
this.checkLoading = false;
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,20 +159,7 @@
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<roll-popup
|
||||
v-if="selectedSpell && selectedSpell.attackRoll"
|
||||
text
|
||||
color="primary"
|
||||
class="mx-2"
|
||||
:disabled="!canCast"
|
||||
:name="selectedSpell.name"
|
||||
:advantage="selectedSpell.attackRoll && selectedSpell.attackRoll.advantage"
|
||||
@roll="cast"
|
||||
>
|
||||
Cast
|
||||
</roll-popup>
|
||||
<v-btn
|
||||
v-else
|
||||
text
|
||||
:disabled="!canCast"
|
||||
class="mx-2 px-4"
|
||||
@@ -192,7 +179,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import spellsWithSubheaders from '/imports/client/ui/properties/components/spells/spellsWithSubheaders';
|
||||
import SpellSlotListTile from '/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import SpellListTile from '/imports/client/ui/properties/components/spells/SpellListTile.vue';
|
||||
import RollPopup from '/imports/client/ui/components/RollPopup.vue';
|
||||
import { find } from 'lodash';
|
||||
|
||||
const slotFilter = {
|
||||
@@ -207,7 +193,6 @@ const slotFilter = {
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
RollPopup,
|
||||
SplitListLayout,
|
||||
SpellSlotListTile,
|
||||
SpellListTile,
|
||||
@@ -403,7 +388,7 @@ export default {
|
||||
};
|
||||
}
|
||||
return CreatureProperties.find(filter, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
},
|
||||
spellSlots() {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function createListOfProperties(filter = {}, getNamesWithValues)
|
||||
});
|
||||
}
|
||||
}
|
||||
let options = { sort: { order: 1, variableName: 1 } }
|
||||
let options = { sort: { left: 1, variableName: 1 } }
|
||||
CreatureProperties.find(filter, options).forEach(addUniquePropertys);
|
||||
LibraryNodes.find(filter, options).forEach(addUniquePropertys);
|
||||
if (getNamesWithValues) return propertyList;
|
||||
|
||||
@@ -134,9 +134,9 @@
|
||||
name="Child properties"
|
||||
:cols="{cols: 12}"
|
||||
>
|
||||
<creature-properties-tree
|
||||
<descendant-properties-tree
|
||||
style="width: 100%;"
|
||||
:root="{collection, id: model._id}"
|
||||
:model="model"
|
||||
:collection="collection"
|
||||
@length="childrenLength = $event"
|
||||
@selected="selectSubProperty"
|
||||
@@ -155,14 +155,14 @@ import CreaturePropertiesTree from '/imports/client/ui/creature/creatureProperti
|
||||
import PropertyField from '/imports/client/ui/properties/viewers/shared/PropertyField.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import DescendantPropertiesTree from '/imports/client/ui/creature/creatureProperties/DescendantPropertiesTree.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
...propertyViewerIndex,
|
||||
CreaturePropertiesTree,
|
||||
PropertyField,
|
||||
TreeNodeView,
|
||||
DescendantPropertiesTree,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
|
||||
@@ -141,12 +141,13 @@
|
||||
import propertyViewerMixin from '/imports/client/ui/properties/viewers/shared/propertyViewerMixin'
|
||||
import numberToSignedString from '../../../../api/utility/numberToSignedString';
|
||||
import AttributeEffect from '/imports/client/ui/properties/components/attributes/AttributeEffect.vue';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
||||
import IncrementButton from '/imports/client/ui/components/IncrementButton.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import getProficiencyIcon from '/imports/client/ui/utility/getProficiencyIcon';
|
||||
import {snackbar} from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import sortEffects from '/imports/client/ui/utility/sortEffects';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -208,18 +209,24 @@
|
||||
data: {_id: id},
|
||||
});
|
||||
},
|
||||
damageProperty({type, value}) {
|
||||
damageProperty({ type, value }) {
|
||||
const model = this.model;
|
||||
this.damagePropertyLoading = true;
|
||||
damageProperty.call({
|
||||
_id: this.model._id,
|
||||
operation: type,
|
||||
value: value
|
||||
}, error => {
|
||||
this.damagePropertyLoading = false;
|
||||
if (error){
|
||||
snackbar({text: error.reason});
|
||||
console.error(error);
|
||||
doAction(model, this.$store, model._id, {
|
||||
subtaskFn: 'damageProp',
|
||||
prop: model,
|
||||
targetIds: [model.root.id],
|
||||
params: {
|
||||
title: getPropertyTitle(model),
|
||||
operation: type,
|
||||
value,
|
||||
targetProp: model,
|
||||
}
|
||||
}).catch((error) => {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
}).finally(() => {
|
||||
this.damagePropertyLoading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
return CreatureProperties.find({
|
||||
_id: {$in: this.model.proficiencyIds},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).fetch();
|
||||
},
|
||||
ability() {
|
||||
|
||||
@@ -10,36 +10,7 @@
|
||||
class="avatar"
|
||||
:style="{ opacity: active ? '' : '0.5'}"
|
||||
>
|
||||
<roll-popup
|
||||
v-if="rollBonus"
|
||||
:icon="!active"
|
||||
:outlined="!active"
|
||||
:fab="active"
|
||||
style="letter-spacing: normal;"
|
||||
class="mr-2"
|
||||
:no-click="!active"
|
||||
:style="{
|
||||
fontSize: active ? '24px' : '16px'
|
||||
}"
|
||||
:large="active"
|
||||
:color="model.color || 'primary'"
|
||||
:loading="doActionLoading"
|
||||
:disabled="model.insufficientResources || !context.editPermission || !!targetingError"
|
||||
:roll-text="rollBonus"
|
||||
:name="model.name"
|
||||
:advantage="model.attackRoll && model.attackRoll.advantage"
|
||||
@roll="doAction"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
/>
|
||||
</roll-popup>
|
||||
<v-btn
|
||||
v-else
|
||||
:icon="!active"
|
||||
:outlined="!active"
|
||||
:fab="active"
|
||||
@@ -157,7 +128,6 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||
import AttributeConsumedView from '/imports/client/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/client/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
|
||||
import RollPopup from '/imports/client/ui/components/RollPopup.vue';
|
||||
import MarkdownText from '/imports/client/ui/components/MarkdownText.vue';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||
@@ -172,7 +142,6 @@ export default {
|
||||
ItemConsumedView,
|
||||
MarkdownText,
|
||||
PropertyIcon,
|
||||
RollPopup,
|
||||
CardHighlight,
|
||||
TreeNodeList,
|
||||
},
|
||||
@@ -248,7 +217,7 @@ export default {
|
||||
'ancestors.id': this.model._id,
|
||||
'removed': { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: {left: 1}
|
||||
}).map(prop => {
|
||||
// Get all the props we don't want to show the decendants of and
|
||||
// where they might appear in the ancestor list
|
||||
|
||||
@@ -149,7 +149,7 @@ const getProperties = function (creatureId, selector = {}) {
|
||||
],
|
||||
...selector,
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
sort: { left: 1 }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ export default {
|
||||
const propsById = {};
|
||||
const props = [];
|
||||
CreatureProperties.find(filter, {
|
||||
sort: { order: -1 },
|
||||
sort: { left: -1 },
|
||||
fields: { _id: 1, type: 1 },
|
||||
}).forEach(prop => {
|
||||
props.push(prop);
|
||||
|
||||
@@ -243,7 +243,7 @@ Meteor.publish('libraryNodes', function (libraryId, extraFields) {
|
||||
LibraryNodes.find({
|
||||
'root.id': libraryId,
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
fields,
|
||||
}),
|
||||
];
|
||||
@@ -288,7 +288,7 @@ Meteor.publish('softRemovedLibraryNodes', function (libraryId) {
|
||||
removed: true,
|
||||
removedWith: { $exists: false },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}),
|
||||
];
|
||||
});
|
||||
@@ -309,7 +309,7 @@ Meteor.publish('descendantLibraryNodes', function (nodeId) {
|
||||
LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
sort: { left: 1 },
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user