Refactored actions, 'cast a spell' task now works
This commit is contained in:
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
99
app/imports/api/engine/action/tasks/applyCastSpellTask.ts
Normal file
99
app/imports/api/engine/action/tasks/applyCastSpellTask.ts
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user