Fixed a lot of UI to match new parenting API

This commit is contained in:
Thaum Rystra
2024-05-08 12:10:43 +02:00
parent 620634c6fd
commit 4a52c3af19
19 changed files with 108 additions and 77 deletions

View File

@@ -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',

View File

@@ -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({

View File

@@ -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',
},
],

View File

@@ -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: [{

View File

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

View File

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

View File

@@ -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'
},

View File

@@ -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';

View File

@@ -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,

View File

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

View File

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

View File

@@ -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},

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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},

View File

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

View File

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

View File

@@ -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: [

View File

@@ -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,