Refactored actions, 'cast a spell' task now works

This commit is contained in:
Thaum Rystra
2024-10-28 12:28:36 +02:00
parent 804c5f3aee
commit 8f8c9c28aa
39 changed files with 423 additions and 399 deletions

View File

@@ -1,16 +1,24 @@
import { CheckParams } from '/imports/api/engine/action/functions/userInput/InputProvider';
type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask | ResetTask;
type Task = PropTask | DamagePropTask | ItemAsAmmoTask | CheckTask | ResetTask | CastSpellTask;
export default Task;
type BaseTask = {
prop: { type: string, [key: string]: any };
targetIds: string[];
silent?: boolean | undefined;
}
type Prop = {
_id: string;
type: string;
[key: string]: any,
}
export type PropTask = BaseTask & {
subtaskFn?: undefined,
prop: Prop;
subtaskFn?: undefined;
silent?: undefined;
}
export type DamagePropTask = BaseTask & {
@@ -22,12 +30,14 @@ export type DamagePropTask = BaseTask & {
title?: string;
operation: 'increment' | 'set';
value: number;
targetProp: any;
targetProp: Prop;
};
}
export type ItemAsAmmoTask = BaseTask & {
subtaskFn: 'consumeItemAsAmmo';
prop: Prop;
silent?: undefined;
params: {
value: number;
item: any;
@@ -40,8 +50,17 @@ export type CheckTask = BaseTask & CheckParams & {
}
export type ResetTask = BaseTask & {
subtaskFn: 'reset',
subtaskFn: 'reset';
eventName: string;
// One and only one target
targetIds: [string];
}
export type CastSpellTask = BaseTask & {
prop?: Prop | undefined;
silent?: undefined;
subtaskFn: 'castSpell';
params: {
spellId: string | undefined;
};
}

View File

@@ -6,7 +6,6 @@ import Context from '../../../../parser/types/Context';
* Each mutation may apply to a different subset of targets
*/
export default class TaskResult {
propId: string;
// The targets of the original task
targetIds: string[];
scope: any;
@@ -21,8 +20,7 @@ export default class TaskResult {
// properties can be found on variable.previous
pushScope?: any;
mutations: Mutation[];
constructor(propId: string, targetIds: string[]) {
this.propId = propId;
constructor(targetIds: string[]) {
this.targetIds = targetIds;
this.mutations = [];
this.scope = {};

View File

@@ -0,0 +1,99 @@
import { EngineAction } from '/imports/api/engine/action/EngineActions';
import { CastSpellTask } from '/imports/api/engine/action/tasks/Task';
import TaskResult from './TaskResult';
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
import { getPropertiesOfType, getSingleProperty } from '/imports/api/engine/loadCreatures';
import applyTask from '/imports/api/engine/action/tasks/applyTask';
import applyActionProperty from '../applyProperties/applyActionProperty';
export default async function applySpellProperty(
task: CastSpellTask, action: EngineAction, result: TaskResult, userInput: InputProvider
): Promise<void> {
let prop = task.prop;
// Ask the user how this spell is being cast
const castOptions = await userInput.castSpell({
spellId: task.params.spellId,
slotId: prop?.castWithoutSpellSlots
? undefined
: getSuggestedSpellSlotId(action.creatureId, prop),
ritual: false,
});
if (!castOptions.spellId) {
result.appendLog({
name: 'Error casting spell',
value: 'No spell was selected',
}, [action.creatureId]);
return;
}
// If the user changed the spell they are casting, use that as the prop
prop = getSingleProperty(action.creatureId, castOptions.spellId);
if (!prop) {
result.appendLog({
name: 'Error casting spell',
value: 'The chosen spell was not found',
}, [action.creatureId]);
return;
}
let slotLevel = prop.level || 0;
// Get the slot being cast with
const slot = castOptions.slotId && getSingleProperty(action.creatureId, castOptions.slotId);
// Log casting method
logCastingMessage(slot?.spellSlotLevel?.value, castOptions, result, prop, task.targetIds);
// Spend the spell slot and change the spell's casting level if a slot is used
if (slot) {
await spendSpellSlot(action, castOptions, userInput);
slotLevel = slot.spellSlotLevel?.value || 0;
}
// Add the slot level to the scope
result.pushScope = {
'~slotLevel': { value: slotLevel },
'slotLevel': { value: slotLevel },
};
// Run the rest of the spell as if it were an action
return applyActionProperty({
prop,
targetIds: task.targetIds,
}, action, result, userInput);
}
function getSuggestedSpellSlotId(creatureId, prop) {
if (!prop) return;
const slots = getPropertiesOfType(creatureId, 'spellSlot')
.sort((a, b) => a.spellSlotLevel?.value - b.spellSlotLevel?.value)
.filter(slot => slot.spellSlotLevel.value > prop.level);
return slots[0]?._id;
}
function logCastingMessage(slotLevel: number, castOptions, result: TaskResult, prop, targetIds: string[]) {
let message = '';
// Determine which message to post
if (slotLevel) {
message = `Casting using a level ${slotLevel} spell slot`
} else if (prop.level) {
if (castOptions.ritual) {
message = `Ritual casting at level ${slotLevel}`
} else {
message = `Casting at level ${slotLevel}`
}
}
// Post the message
if (message) {
result.appendLog({
name: `Casting at level ${slotLevel}`
}, targetIds);
}
}
function spendSpellSlot(action, castOptions, userInput) {
const slot = getSingleProperty(action.creatureId, castOptions.slotId);
return applyTask(action, {
targetIds: [action.creatureId],
subtaskFn: 'damageProp',
params: {
operation: 'increment',
value: 1,
targetProp: slot,
},
}, userInput);
}

View File

@@ -14,7 +14,6 @@ import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
export default async function applyCheckTask(
task: CheckTask, action: EngineAction, result: TaskResult, userInput: InputProvider
): Promise<void> {
const prop = task.prop;
const targetIds = task.targetIds;
if (task.contest) {
@@ -45,7 +44,7 @@ export default async function applyCheckTask(
if (skill || ability) {
// Create a new result after before triggers have run
result = new TaskResult(task.prop._id, task.targetIds);
result = new TaskResult(task.targetIds);
action.results.push(result);
}
@@ -75,7 +74,7 @@ export default async function applyCheckTask(
name: checkName,
inline: true,
...dc !== null && { value: `DC **${dc}**` },
...prop?.silent && { silenced: prop.silent }
...task?.silent && { silenced: task.silent }
}, [targetId]);
// Roll the dice
@@ -109,7 +108,7 @@ export default async function applyCheckTask(
name: rollName,
value: `${resultPrefix}\n**${totalValue}**`,
inline: true,
...prop?.silent && { silenced: prop.silent }
...task?.silent && { silenced: task.silent }
}, [targetId]);
// After check triggers

View File

@@ -10,8 +10,6 @@ import numberToSignedString from '/imports/api/utility/numberToSignedString';
export default async function applyDamagePropTask(
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
): Promise<number> {
const prop = task.prop;
if (task.targetIds.length > 1) {
throw 'This subtask can only be called on a single target';
}
@@ -45,7 +43,7 @@ export default async function applyDamagePropTask(
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.before', userInput);
// Create a new result after triggers have run
result = new TaskResult(task.prop._id, task.targetIds);
result = new TaskResult(task.targetIds);
action.results.push(result);
// Refetch the scope properties
@@ -75,7 +73,7 @@ export default async function applyDamagePropTask(
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
` ${value}`,
inline: true,
...prop.silent && { silenced: true },
...task.silent && { silenced: true },
}, task.targetIds);
}
@@ -106,7 +104,7 @@ export default async function applyDamagePropTask(
name: title,
value: `${getPropertyTitle(targetProp)} set from ${targetProp.value} to ${value}`,
inline: true,
...prop.silent && { silenced: true },
...task.silent && { silenced: true },
}]
});
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
@@ -132,7 +130,7 @@ export default async function applyDamagePropTask(
name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored',
value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`,
inline: true,
...prop.silent && { silenced: true },
...task.silent && { silenced: true },
}]
});
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
@@ -159,4 +157,4 @@ function setScope(result, targetProp, newValue, damage) {
value: newValue,
damage,
};
}
}

View File

@@ -25,7 +25,7 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
await applyTriggers(action, item, task.targetIds, 'ammoTriggerIds.before', userInput);
// Create a new result after before triggers have run
result = new TaskResult(task.prop._id, task.targetIds);
result = new TaskResult(task.targetIds);
action.results.push(result);
// Refetch the scope properties
@@ -51,7 +51,7 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
contents: [{
name: getPropertyTitle(item) || 'Ammo',
inline: false,
silenced: prop.silent,
...prop?.silent && { silenced: true },
}]
},
});
@@ -64,4 +64,4 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
await applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
}
return applyTriggers(action, item, task.targetIds, 'ammoTriggerIds.afterChildren', userInput);
}
}

View File

@@ -75,13 +75,12 @@ export async function resetProperties(task: ResetTask, action: EngineAction, res
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,
value: -prop.damage || 0,
targetProp: prop,
},
}, userInput);
@@ -152,7 +151,6 @@ async function resetHitDice(task: ResetTask, action: EngineAction, result: TaskR
// Apply the damage prop task
await applyTask(action, {
prop: task.prop || hd,
targetIds: [creatureId],
subtaskFn: 'damageProp',
params: {

View File

@@ -8,6 +8,7 @@ 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';
import applyCastSpellTask from '/imports/api/engine/action/tasks/applyCastSpellTask';
// DamagePropTask promises a number of actual damage done
export default async function applyTask(
@@ -37,7 +38,7 @@ export default async function applyTask(
if (action.taskCount > 100) throw 'Only 100 properties can be applied at once';
if (task.subtaskFn) {
const result = new TaskResult(task.prop._id, task.targetIds);
const result = new TaskResult(task.targetIds);
action.results.push(result);
switch (task.subtaskFn) {
case 'damageProp':
@@ -48,6 +49,8 @@ export default async function applyTask(
return applyCheckTask(task, action, result, inputProvider);
case 'reset':
return applyResetTask(task, action, result, inputProvider);
case 'castSpell':
return applyCastSpellTask(task, action, result, inputProvider);
default:
throw 'No case defined for the given subtaskFn';
}
@@ -71,11 +74,11 @@ export default async function applyTask(
}
// Create a result an push it to the action results, pass it to the apply function to modify
const result = new TaskResult(task.prop._id, task.targetIds);
const result = new TaskResult(task.targetIds);
result.scope[`#${prop.type}`] = { _propId: prop._id };
action.results.push(result);
// Apply the property
return applyProperties[prop.type]?.(task, action, result, inputProvider);
}
}
}