Started fixing action target selection

This commit is contained in:
ThaumRystra
2024-06-12 15:43:56 +02:00
parent 4921a34dfe
commit a5292cf0f2
17 changed files with 162 additions and 15 deletions

View File

@@ -35,7 +35,7 @@ const ActionSchema = new SimpleSchema({
},
targetIds: {
type: Array,
defaultValue: [],
optional: true,
},
'targetIds.$': {
type: String,

View File

@@ -40,14 +40,28 @@ export default async function applyAction(action: EngineAction, userInput: Input
action._isSimulation = simulate;
action.taskCount = 0;
let task = options?.task;
console.log('task', task, action.targetIds)
if (!task) {
const prop = await getSingleProperty(action.creatureId, action.rootPropId);
if (!prop) throw new Meteor.Error('Not found', 'Root action property could not be found');
// If the target ids weren't already set, get them from the user
console.log(action.targetIds, prop);
if (!action.targetIds && (
prop.target === 'singleTarget' ||
prop.target === 'multipleTargets'
)) {
console.log('getting targetIds');
action.targetIds = await (userInput.targetIds(prop.targets));
console.log('got targetIds', action.targetIds);
}
task = {
prop,
targetIds: action.targetIds || [],
}
}
await applyTask(action, task, userInput);
return action;
}

View File

@@ -63,6 +63,7 @@ export default async function spendResources(
!quantity ||
!isFinite(quantity)
) continue;
await applyTask(action, {
prop,
targetIds,

View File

@@ -1,6 +1,10 @@
import Task from '/imports/api/engine/action/tasks/Task';
type InputProvider = {
/**
* Get the ids of the creatures being targeted
*/
targetIds(target: 'singleTarget' | 'multipleTargets', currentTargetIds?: string[]): Promise<string[]>;
/**
* Show the user the next property or task to apply and wait for input to continue
*/

View File

@@ -8,6 +8,9 @@ export default function getReplayChoicesInputProvider(actionId: string, decision
const decisionStack = [...decisions].reverse();
const dRoller = getDeterministicDiceRoller(actionId);
const replaySavedInput: InputProvider = {
targetIds() {
return Promise.resolve(decisionStack.pop());
},
nextStep() {
return Promise.resolve();
},

View File

@@ -1,6 +1,9 @@
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
const inputProviderForTests: InputProvider = {
async targetIds(target, currentTargetIds = []) {
return currentTargetIds;
},
/**
* For testing, randomness is hard to deal with
* rollDice function returns the average roll for every dice rolled

View File

@@ -47,7 +47,7 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
type: 'item',
}],
// Log the item name as a heading if it has child properties to apply
...itemChildren.length && {
...itemChildren.length && !task.params.skipChildren && {
contents: [{
name: getPropertyTitle(item) || 'Ammo',
inline: false,

View File

@@ -651,8 +651,12 @@ export function hasAncestorRelationship(propA: TreeDoc, propB: TreeDoc): boolean
if (propA.root.id !== propB.root.id) {
return false;
}
// Return if there is an ancestor relationship in either direction
return isAncestor(propA, propB) || isAncestor(propB, propA);
// Return if there is an parent relationship in either direction
return propA.parentId === propB._id
|| propB.parentId === propA._id
// or an ancestor relationship in either direction
|| isAncestor(propA, propB)
|| isAncestor(propB, propA);
}
/**
@@ -856,4 +860,5 @@ function writeBulkOperations(collection: Mongo.Collection<TreeDoc>, operations)
}
});
}
return Promise.resolve();
}

View File

@@ -86,7 +86,8 @@ import ChoiceInput from '/imports/client/ui/creature/actions/input/ChoiceInput.v
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';
//import RollInput from '/imports/client/ui/creature/actions/input/RollInput.vue';
import TargetsInput from '/imports/client/ui/creature/actions/input/TargetsInput.vue';
export default {
components: {
@@ -95,7 +96,8 @@ export default {
ChoiceInput,
DialogBase,
LogContent,
RollInput,
//RollInput,
TargetsInput,
},
props: {
actionId: {
@@ -203,6 +205,16 @@ export default {
this.$store.dispatch('popDialogStack');
},
// inputProvider methods
async targetIds(target) {
console.log('input provider UI targetIds')
this.userInput = [];
this.activeInputParams = {
target,
tabletopId: this.action.tabletopId,
};
this.activeInput = 'targets-input'
return this.promiseInput();
},
async rollDice(dice) {
return Promise.resolve(this.deterministicDiceRoller(dice));
/* Dice Animation and user control goes here:

View File

@@ -79,6 +79,7 @@ const throwInputRequestedError = () => {
function getErrorOnInputRequestProvider(actionId) {
const errorOnInputRequest: InputProvider = {
targetIds: throwInputRequestedError,
nextStep: throwInputRequestedError,
rollDice: getDeterministicDiceRoller(actionId),
choose: throwInputRequestedError,

View File

@@ -0,0 +1,66 @@
<template>
<div class="choice-input">
<creature-list-tile
v-for="creature in creatures"
:key="creature._id"
:model="creature"
selection
:selected="value.includes(creature._id)"
@click="selectCreature(creature._id)"
/>
<v-btn
@click="$emit('continue');"
>
Done
</v-btn>
</div>
</template>
<script lang="js">
import CreatureListTile from '/imports/client/ui/creature/creatureList/CreatureListTile.vue';
import Creatures from '/imports/api/creature/creatures/Creatures';
export default {
components: {
CreatureListTile
},
props: {
value: {
type: Array,
required: true,
},
target: {
type: String,
default: 'multipleTargets',
},
tabletopId: {
type: String,
required: true
},
},
methods: {
selectCreature(id) {
let newValue;
if (this.value.includes(id)) {
newValue = this.value.filter((creatureId) => creatureId !== id);
} else if (this.target === 'singleTarget') {
newValue = [id];
} else {
newValue = [...this.value, id];
}
this.$emit('input', newValue);
},
},
meteor: {
creatures() {
return Creatures.find({
tabletopId: this.tabletopId,
}, {
sort: {
name: 1,
},
}).fetch();
}
}
};
</script>

View File

@@ -16,7 +16,7 @@
class="creature"
:model="creature"
:selection="selection"
:is-selected="selectedCreature === creature._id"
:is-selected="selectedCreature === creature._id || selectedCreatures.has(creature._id)"
v-bind="selection ? {} : {to: creature.url}"
:dense="dense"
:data-id="dense ? undefined : creature._id"
@@ -50,6 +50,10 @@
type: String,
default: undefined,
},
selectedCreatures: {
type: Set,
default: () => new Set(),
},
dense: Boolean,
},
data(){return {

View File

@@ -216,6 +216,7 @@ export default {
},
doAction() {
this.doActionLoading = true;
this.$emit('close-menu')
doAction(this.model, this.$store, this.model._id).catch((e) => {
console.error(e);
snackbar({ text: e.message || e.reason || e.toString() });

View File

@@ -89,6 +89,7 @@
<selected-creature-bar
:key="activeCreatureId"
:creature-id="activeCreatureId"
@active-action-change="activeActionId = $event"
/>
</v-slide-y-reverse-transition>
</v-footer>
@@ -167,6 +168,7 @@ export default {
},
moreTargets(){
const activeAction = CreatureProperties.findOne(this.activeActionId);
console.log(this.activeActionId, activeAction)
if (!activeAction) return;
if (activeAction.target === 'singleTarget') {
return this.targets.length === 0;

View File

@@ -1,6 +1,6 @@
<template lang="html">
<v-card
:style="`height: ${height}px; width: ${width}px; overflow: hidden;`"
:style="`height: ${height}px; width: ${width}px;`"
class="tabletop-creature-card"
:class="{ active }"
:hover="hasClickListener"
@@ -31,7 +31,6 @@
style="opacity: 0.7; margin-top: 2px"
/>
</v-img>
<card-highlight :active="hover" />
<div class="d-flex justify-center">
<v-scale-transition>
<v-btn
@@ -40,7 +39,7 @@
:elevation="targeted ? 8 : 2"
fab
small
@click.stop="targeted ? $emit('untarget') : $emit('target')"
@click.stop.prevent="targeted ? $emit('untarget') : $emit('target')"
>
<v-icon>{{ targeted ? 'mdi-target' : 'mdi-target' }}</v-icon>
</v-btn>

View File

@@ -29,6 +29,7 @@
transition: 'opacity 0.2s ease',
}"
:model="selectedProp"
@close-menu="menuOpen = false"
/>
<v-card
v-else-if="activeIcon && activeIcon.tab"
@@ -180,6 +181,12 @@ export default {
this.selectedIcon = undefined;
}
},
selectedIcon: {
immediate: true,
handler: function ({ propId } = {}) {
this.$emit('active-action-change', propId)
}
}
},
methods: {
log(e) {
@@ -217,15 +224,17 @@ export default {
this.menuX = x;
this.selectedIcon = icon;
this.menuOpen = true;
},
},
clickOutsideMenu () {
this.menuOpen = false;
},
menuClickOutsideInclude() {
return compact([
const outside = compact([
document.querySelector('.selected-creature-bar'),
document.querySelector('.tabletop-prop-menu')
...document.querySelectorAll('.tabletop-creature-card'),
document.querySelector('.tabletop-prop-menu'),
]);
return outside;
},
openCharacterSheet(tab, elementId) {
this.$store.commit(

View File

@@ -29,12 +29,19 @@ Meteor.publish('singleCharacter', function (creatureId) {
let permissionCreature = Creatures.findOne({
_id: creatureId,
}, {
fields: { owner: 1, readers: 1, writers: 1, public: 1, computeVersion: 1 }
fields: {
owner: 1,
readers: 1,
writers: 1,
public: 1,
computeVersion: 1,
tabletopId: 1,
}
});
try { assertViewPermission(permissionCreature, userId) }
catch (e) { return [] }
loadCreature(creatureId, self);
if (permissionCreature.computeVersion !== VERSION && computation.firstRun) {
if (permissionCreature?.computeVersion !== VERSION && computation.firstRun) {
try {
rebuildCreatureNestedSets(creatureId).then(() => {
try {
@@ -71,6 +78,22 @@ Meteor.publish('singleCharacter', function (creatureId) {
username: 1,
},
}),
// Also publish summaries of creatures in the same tabletop
Creatures.find({
tabletopId: permissionCreature?.tabletopId,
}, {
fields: {
_id: 1,
name: 1,
picture: 1,
avatarPicture: 1,
tabletopId: 1,
initiativeRoll: 1,
settings: 1,
propCount: 1,
},
limit: 110, // Party vs 100 creatures was a fun encounter to run, so let's support that
}),
];
});
});