Fixed a lot of UI to match new parenting API
This commit is contained in:
@@ -15,6 +15,8 @@ const updateCreatureProperty = new ValidatedMethod({
|
||||
case 'parent':
|
||||
case 'ancestors':
|
||||
case 'root':
|
||||
case 'left':
|
||||
case 'right':
|
||||
case 'parentId':
|
||||
case 'damage':
|
||||
throw new Meteor.Error('Permission denied',
|
||||
|
||||
@@ -10,6 +10,7 @@ import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineAc
|
||||
import applyAction from '/imports/api/engine/action/functions/applyAction';
|
||||
import { LogContent, Removal, Update } from '/imports/api/engine/action/tasks/TaskResult';
|
||||
import inputProvider from './functions/userInput/inputProviderForTests.testFn';
|
||||
import { removeAllCreaturesAndProps } from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
|
||||
const creatureId = Random.id();
|
||||
const targetId = Random.id();
|
||||
@@ -19,11 +20,8 @@ describe('Interrupt action system', function () {
|
||||
this.timeout(8000);
|
||||
before(async function () {
|
||||
// Remove old data
|
||||
await Promise.all([
|
||||
CreatureProperties.removeAsync({}),
|
||||
Creatures.removeAsync({}),
|
||||
CreatureVariables.removeAsync({}),
|
||||
]);
|
||||
await removeAllCreaturesAndProps();
|
||||
|
||||
// Add creatures
|
||||
await Promise.all([
|
||||
Creatures.insertAsync({
|
||||
|
||||
@@ -16,7 +16,7 @@ const [
|
||||
creatureId, targetCreatureId, targetCreature2Id, emptyActionId, selfActionId, attackActionId,
|
||||
usesActionId, attackMissId, attackNoTargetId, usesResourcesActionId, ammoId, resourceAttId,
|
||||
consumeAmmoId, consumeResourceId, noUsesActionId, insufficientResourcesActionId,
|
||||
attributeResetByEventId, eventActionId, advantageAttackId, advantageEffectId
|
||||
attributeResetByEventId, eventActionId, advantageAttackId, advantageEffectId, disadvantageAttackId, disadvantageEffectId,
|
||||
] = randomIds;
|
||||
|
||||
const actionTestCreature = {
|
||||
@@ -60,6 +60,20 @@ const actionTestCreature = {
|
||||
targetByTags: true,
|
||||
targetTags: ['hasAdvantage'],
|
||||
},
|
||||
// Attack that has Disadvantage
|
||||
{
|
||||
_id: disadvantageAttackId,
|
||||
type: 'action',
|
||||
attackRoll: { calculation: '0' },
|
||||
tags: ['hasDisadvantage'],
|
||||
},
|
||||
{
|
||||
_id: disadvantageEffectId,
|
||||
type: 'effect',
|
||||
operation: 'disadvantage',
|
||||
targetByTags: true,
|
||||
targetTags: ['hasDisadvantage'],
|
||||
},
|
||||
// Attack that has no target
|
||||
{
|
||||
_id: attackNoTargetId,
|
||||
@@ -333,6 +347,26 @@ describe('Apply Action Properties', function () {
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
it('should make attack rolls that roll with disadvantage', async function () {
|
||||
const prop = await CreatureProperties.findOneAsync(disadvantageAttackId);
|
||||
assert.equal(prop.attackRoll.disadvantage, 1, 'The attack roll should have disadvantage');
|
||||
const action = await runActionById(disadvantageAttackId, [targetCreatureId]);
|
||||
const expectedMutations: Mutation[] = [
|
||||
{
|
||||
contents: [{ name: 'Action' }],
|
||||
targetIds: [targetCreatureId],
|
||||
}, {
|
||||
contents: [{
|
||||
inline: true,
|
||||
name: 'Hit! (Disadvantage)',
|
||||
value: '1d20 [ 10, ~~11~~ ] + 0\n**10**',
|
||||
}],
|
||||
targetIds: [targetCreatureId],
|
||||
}
|
||||
];
|
||||
assert.deepEqual(allMutations(action), expectedMutations);
|
||||
});
|
||||
|
||||
it('actions should consume resources', async function () {
|
||||
const action = await runActionById(usesResourcesActionId, []);
|
||||
const expectedMutations: Mutation[] = [
|
||||
@@ -401,7 +435,7 @@ describe('Apply Action Properties', function () {
|
||||
contents: [
|
||||
{
|
||||
inline: true,
|
||||
name: 'Attribute damaged',
|
||||
name: 'Attribute restored',
|
||||
value: '+13 Attribute Reset By testEvent Event',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import { PropTask } from '../tasks/Task';
|
||||
import TaskResult, { LogContent } from '../tasks/TaskResult';
|
||||
import { getPropertiesOfType, getVariables } from '/imports/api/engine/loadCreatures';
|
||||
import applyTask from '/imports/api/engine/action/tasks/applyTask';
|
||||
import { getVariables } from '/imports/api/engine/loadCreatures';
|
||||
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
||||
import recalculateInlineCalculations from '/imports/api/engine/action/functions/recalculateInlineCalculations';
|
||||
import spendResources from '/imports/api/engine/action/functions/spendResources';
|
||||
@@ -115,7 +114,7 @@ async function applyAttackToTarget(
|
||||
if (targetArmor !== undefined) {
|
||||
let name = criticalHit ? 'Critical Hit!' :
|
||||
criticalMiss ? 'Critical Miss!' :
|
||||
result > targetArmor ? 'Hit!' : 'Miss!';
|
||||
result >= targetArmor ? 'Hit!' : 'Miss!';
|
||||
if (advantage === 1) {
|
||||
name += ' (Advantage)';
|
||||
} else if (advantage === -1) {
|
||||
@@ -170,18 +169,19 @@ async function applyAttackWithoutTarget(action, prop, attack, taskResult: TaskRe
|
||||
result,
|
||||
criticalHit,
|
||||
criticalMiss,
|
||||
advantage,
|
||||
} = await rollAttack(attack, scope, taskResult.pushScope, userInput);
|
||||
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
|
||||
if (scope['~attackAdvantage']?.value === 1) {
|
||||
if (advantage === 1) {
|
||||
name += ' (Advantage)';
|
||||
} else if (scope['~attackAdvantage']?.value === -1) {
|
||||
} else if (advantage === -1) {
|
||||
name += ' (Disadvantage)';
|
||||
}
|
||||
if (!criticalMiss) {
|
||||
scope['~attackHit'] = { value: true }
|
||||
taskResult.pushScope['~attackHit'] = { value: true }
|
||||
}
|
||||
if (!criticalHit) {
|
||||
scope['~attackMiss'] = { value: true };
|
||||
taskResult.pushScope['~attackMiss'] = { value: true };
|
||||
}
|
||||
taskResult.mutations.push({
|
||||
contents: [{
|
||||
|
||||
@@ -13,11 +13,17 @@ import inputProvider from './userInput/inputProviderForTests.testFn';
|
||||
* Removes all creatures, properties, and creatureVariable documents from the database
|
||||
*/
|
||||
export async function removeAllCreaturesAndProps() {
|
||||
return Promise.all([
|
||||
CreatureProperties.removeAsync({}),
|
||||
Creatures.removeAsync({}),
|
||||
CreatureVariables.removeAsync({}),
|
||||
]);
|
||||
if (Meteor.isServer) {
|
||||
return Promise.all([
|
||||
CreatureProperties.removeAsync({}),
|
||||
Creatures.removeAsync({}),
|
||||
CreatureVariables.removeAsync({}),
|
||||
]);
|
||||
} else {
|
||||
CreatureProperties.find({}).forEach(doc => CreatureProperties.remove(doc._id));
|
||||
Creatures.find({}).forEach(doc => Creatures.remove(doc._id));
|
||||
CreatureVariables.find({}).forEach((doc: any) => CreatureVariables.remove(doc._id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,20 +17,15 @@ export default async function applyResetTask(
|
||||
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;
|
||||
// Print a title for rest events
|
||||
switch (task.eventName) {
|
||||
case 'shortRest':
|
||||
name = 'Short Rest';
|
||||
result.appendLog({ name: 'Short Rest' }, task.targetIds);
|
||||
break;
|
||||
case 'longRest':
|
||||
name = 'Long Rest';
|
||||
break;
|
||||
default:
|
||||
name = task.eventName;
|
||||
result.appendLog({ name: 'Long Rest' }, task.targetIds);
|
||||
break;
|
||||
}
|
||||
result.appendLog({ name }, task.targetIds);
|
||||
|
||||
// Reset the properties by this event name
|
||||
await resetProperties(task, action, result, userInput);
|
||||
|
||||
@@ -55,7 +55,6 @@ var testProperties = [
|
||||
clean({
|
||||
_id: 'attackAction',
|
||||
type: 'action',
|
||||
ancestors: [{ id: 'charId' }],
|
||||
attackRoll: {
|
||||
calculation: '3'
|
||||
},
|
||||
@@ -86,7 +85,6 @@ var testProperties = [
|
||||
clean({
|
||||
_id: 'attackAction2',
|
||||
type: 'action',
|
||||
ancestors: [{ id: 'charId' }],
|
||||
attackRoll: {
|
||||
calculation: '3'
|
||||
},
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import '/imports/api/creature/archive/methods/archiveCreatureToFile';
|
||||
import '/imports/api/creature/archive/methods/restoreCreatureFromFile';
|
||||
import '/imports/api/creature/archive/methods/removeArchiveCreature';
|
||||
import './removeUserImage';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileS
|
||||
import UserImages from '/imports/api/files/userImages/UserImages';
|
||||
|
||||
const removeArchiveCreature = new ValidatedMethod({
|
||||
name: 'ArchiveCreatureFiles.methods.removeArchiveCreature',
|
||||
name: 'ArchiveCreatureFiles.methods.removeUserImage',
|
||||
validate: new SimpleSchema({
|
||||
'fileId': {
|
||||
type: String,
|
||||
|
||||
@@ -149,8 +149,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
'node._ancestorOfMatchedDocument'(value) {
|
||||
this.expanded = !!value ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
|
||||
this.expanded = !!value || isAncestor(this.node, this.selectedNode);
|
||||
},
|
||||
'selectedNode.parentId'() {
|
||||
this.expanded = isAncestor(this.node, this.selectedNode) || this.expanded;
|
||||
|
||||
@@ -88,7 +88,7 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
|
||||
import MarkdownText from '/imports/client/ui/components/MarkdownText.vue';
|
||||
import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
|
||||
import { docsToForest as nodeArrayToTree } from '/imports/api/parenting/parentingFunctions';
|
||||
import { getFilter, docsToForest as nodeArrayToTree } from '/imports/api/parenting/parentingFunctions';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { some } from 'lodash';
|
||||
|
||||
@@ -169,32 +169,31 @@ export default {
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
throw 'TODO: rewrite with nested sets'
|
||||
const indicesOfTerminatingProps = [];
|
||||
const decendants = CreatureProperties.find({
|
||||
'ancestors.id': this.model._id,
|
||||
const rangesToExclude = [];
|
||||
const descendants = CreatureProperties.find({
|
||||
...getFilter.descendants(this.model),
|
||||
'removed': { $ne: true },
|
||||
}, {
|
||||
sort: {left: 1}
|
||||
}).map(prop => {
|
||||
// Get all the props we don't want to show the decendants of and
|
||||
// Get all the props we don't want to show the descendants of and
|
||||
// where they might appear in the ancestor list
|
||||
if (prop.type === 'buff' || prop.type === 'folder') {
|
||||
indicesOfTerminatingProps.push({
|
||||
id: prop._id,
|
||||
ancestorIndex: prop.ancestors.length,
|
||||
rangesToExclude.push({
|
||||
left: prop.left,
|
||||
right: prop.right,
|
||||
});
|
||||
}
|
||||
return prop;
|
||||
}).filter(prop => {
|
||||
// Filter out folders entirely
|
||||
if (prop.type === 'folder') return false;
|
||||
// Filter out decendants of terminating props
|
||||
return !some(indicesOfTerminatingProps, buffIndex => {
|
||||
return prop.ancestors[buffIndex.ancestorIndex]?.id === buffIndex.id;
|
||||
// Filter out descendants of terminating props
|
||||
return !some(rangesToExclude, range => {
|
||||
return prop.left > range.left && prop.right < range.right;
|
||||
});
|
||||
});
|
||||
return nodeArrayToTree(decendants);
|
||||
return nodeArrayToTree(descendants);
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import SlotCard from '/imports/client/ui/creature/slots/SlotCard.vue';
|
||||
import PointBuyCard from '/imports/client/ui/properties/components/pointBuy/PointBuyCard.vue';
|
||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -74,7 +75,7 @@ export default {
|
||||
meteor: {
|
||||
slots() {
|
||||
const folderIds = CreatureProperties.find({
|
||||
'ancestors.id': this.context.creatureId,
|
||||
...getFilter.descendantsOfRoot(this.context.creatureId),
|
||||
type: 'folder',
|
||||
hideStatsGroup: true,
|
||||
removed: { $ne: true },
|
||||
@@ -82,10 +83,8 @@ export default {
|
||||
}, { fields: { _id: 1 } }).map(folder => folder._id);
|
||||
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.context.creatureId,
|
||||
$nin: folderIds,
|
||||
},
|
||||
...getFilter.descendantsOfRoot(this.context.creatureId),
|
||||
'parentId': { $nin: folderIds },
|
||||
type: 'propertySlot',
|
||||
ignored: { $ne: true },
|
||||
$and: [
|
||||
@@ -108,8 +107,8 @@ export default {
|
||||
},
|
||||
pointBuys(){
|
||||
return CreatureProperties.find({
|
||||
...getFilter.descendantsOfRoot(this.context.creatureId),
|
||||
type: 'pointBuy',
|
||||
'ancestors.id': this.context.creatureId,
|
||||
ignored: { $ne: true },
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import removeUserImage from '/imports/api/files/userImages/UserImages/methods/removeUserImage';
|
||||
import removeUserImage from '/imports/api/files/userImages/methods/removeUserImage';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
||||
@@ -109,7 +109,7 @@ import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.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';
|
||||
import { docsToForest as nodeArrayToTree } from '/imports/api/parenting/parentingFunctions';
|
||||
import { getFilter, docsToForest as nodeArrayToTree } from '/imports/api/parenting/parentingFunctions';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { some } from 'lodash';
|
||||
|
||||
@@ -191,31 +191,31 @@ export default {
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
const indicesOfTerminatingProps = [];
|
||||
const decendants = CreatureProperties.find({
|
||||
'ancestors.id': this.model._id,
|
||||
const rangesToExclude = [];
|
||||
const descendants = CreatureProperties.find({
|
||||
...getFilter.descendants(this.model),
|
||||
'removed': { $ne: true },
|
||||
}, {
|
||||
sort: {left: 1}
|
||||
}).map(prop => {
|
||||
// Get all the props we don't want to show the decendants of and
|
||||
// Get all the props we don't want to show the descendants of and
|
||||
// where they might appear in the ancestor list
|
||||
if (prop.type === 'buff' || prop.type === 'folder') {
|
||||
indicesOfTerminatingProps.push({
|
||||
id: prop._id,
|
||||
ancestorIndex: prop.ancestors.length,
|
||||
rangesToExclude.push({
|
||||
left: prop.left,
|
||||
right: prop.right,
|
||||
});
|
||||
}
|
||||
return prop;
|
||||
}).filter(prop => {
|
||||
// Filter out folders entirely
|
||||
if (prop.type === 'folder') return false;
|
||||
// Filter out decendants of terminating props
|
||||
return !some(indicesOfTerminatingProps, buffIndex => {
|
||||
return prop.ancestors[buffIndex.ancestorIndex]?.id === buffIndex.id;
|
||||
// Filter out descendants of terminating props
|
||||
return !some(rangesToExclude, range => {
|
||||
return prop.left > range.left && prop.right < range.right;
|
||||
});
|
||||
});
|
||||
return nodeArrayToTree(decendants);
|
||||
return nodeArrayToTree(descendants);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -23,6 +23,7 @@ import ItemTreeNode from '/imports/client/ui/properties/treeNodeViews/ItemTreeNo
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import selectAmmoItem from '/imports/api/creature/creatureProperties/methods/selectAmmoItem';
|
||||
import { findIndex } from 'lodash';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
export default {
|
||||
components: {
|
||||
ItemTreeNode
|
||||
@@ -40,7 +41,7 @@ export default {
|
||||
meteor: {
|
||||
items(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.action.ancestors[0].id,
|
||||
...getFilter.descendantsOfRoot(this.action.root.id),
|
||||
type: 'item',
|
||||
tags: this.itemConsumed.tag,
|
||||
removed: {$ne: true},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import { docsToForest as nodeArrayToTree } from '/imports/api/parenting/parentingFunctions';
|
||||
import { getFilter, docsToForest } from '/imports/api/parenting/parentingFunctions';
|
||||
import BuildTreeNodeList from '/imports/client/ui/creature/buildTree/BuildTreeNodeList.vue';
|
||||
|
||||
function traverse(tree, callback, parents = []){
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
const slots = CreatureProperties.find({
|
||||
$and: [ {
|
||||
$or: [
|
||||
{ 'ancestors.id': this.model._id, },
|
||||
{ ...getFilter.descendants(this.model) },
|
||||
{ '_id': this.model._id, },
|
||||
],
|
||||
}, {
|
||||
@@ -57,12 +57,12 @@ export default {
|
||||
}, {
|
||||
sort: { left: 1 },
|
||||
});
|
||||
const tree = nodeArrayToTree([
|
||||
const tree = docsToForest([
|
||||
...slots.fetch(),
|
||||
...slotChildren.fetch()
|
||||
]);
|
||||
traverse(tree, (child, parents) => {
|
||||
const model = child.node;
|
||||
const model = child.doc;
|
||||
const isSlotWithSpace = model.type === 'propertySlot' && (
|
||||
model.spaceLeft > 0 ||
|
||||
!model.quantityExpected ||
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
if (isSlotWithSpace) {
|
||||
model._canFill = true;
|
||||
parents.forEach(node => {
|
||||
node.node._descendantCanFill = true;
|
||||
node.doc._descendantCanFill = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,10 +2,11 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import FolderGroupCard from '/imports/client/ui/properties/components/folders/FolderGroupCard.vue';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
function getFolders(creatureId, tab, location) {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
...getFilter.descendantsOfRoot(creatureId),
|
||||
groupStats: true,
|
||||
inactive: { $ne: true },
|
||||
removed: { $ne: true },
|
||||
@@ -13,7 +14,7 @@ function getFolders(creatureId, tab, location) {
|
||||
location,
|
||||
}, {
|
||||
sort: {
|
||||
order: 1,
|
||||
left: 1,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ import spellsWithSubheaders from '/imports/client/ui/properties/components/spell
|
||||
import SpellSlotListTile from '/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import SpellListTile from '/imports/client/ui/properties/components/spells/SpellListTile.vue';
|
||||
import { find } from 'lodash';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
|
||||
const slotFilter = {
|
||||
type: 'attribute',
|
||||
@@ -360,7 +361,7 @@ export default {
|
||||
meteor: {
|
||||
spells() {
|
||||
let filter = {
|
||||
'ancestors.id': this.creatureId,
|
||||
...getFilter.descendantsOfRoot(this.creatureId),
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
$or: [
|
||||
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
},
|
||||
numPrepared() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.model._id,
|
||||
...getFilter.descendants(this.model),
|
||||
type: 'spell',
|
||||
removed: { $ne: true },
|
||||
prepared: true,
|
||||
|
||||
Reference in New Issue
Block a user