Files
DiceCloud/app/imports/api/engine/action/tasks/applyDamagePropTask.ts
2024-10-30 17:53:39 +02:00

161 lines
5.9 KiB
TypeScript

import { EngineAction } from '/imports/api/engine/action/EngineActions';
import { DamagePropTask } from '/imports/api/engine/action/tasks/Task';
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
import { applyTriggers } from '/imports/api/engine/action/functions/applyTaskGroups';
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
import { getSingleProperty } from '/imports/api/engine/loadCreatures';
import numberToSignedString from '/imports/api/utility/numberToSignedString';
export default async function applyDamagePropTask(
task: DamagePropTask, action: EngineAction, result: TaskResult, userInput
): Promise<number> {
if (task.targetIds.length > 1) {
throw 'This subtask can only be called on a single target';
}
const targetId = task.targetIds[0];
let { value } = task.params;
const { title, operation } = task.params;
let targetProp = task.params.targetProp;
if (!targetProp) throw new Meteor.Error('not-found', 'Target property is required')
// Set the scope properties
result.pushScope = {};
if (operation === 'increment') {
if (value >= 0) {
result.pushScope['~damage'] = { value };
} else {
result.pushScope['~healing'] = { value: -value };
}
} else {
result.pushScope['~set'] = { value };
}
// Store which property we're targeting
if (targetId === action.creatureId) {
result.pushScope['~attributeDamaged'] = { _propId: targetProp._id };
} else {
result.pushScope['~attributeDamaged'] = targetProp;
}
// Run the before triggers which may change scope properties
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.before', userInput);
// Create a new result after triggers have run
result = new TaskResult(task.targetIds);
action.results.push(result);
// Refetch the scope properties
const scope = await getEffectiveActionScope(action);
result.popScope = {
'~damage': 1, '~healing': 1, '~set': 1, '~attributeDamaged': 1,
};
value = +value;
if (operation === 'increment') {
if (value >= 0) {
value = scope['~damage']?.value;
} else {
value = -scope['~healing']?.value;
}
} else {
value = scope['~set']?.value;
}
const targetPropId = scope['~attributeDamaged']?._propId ??
scope['~attributeDamaged']?._id;
// If there are no targets, just log the result that would apply and end
if (!task.targetIds?.length) {
// Get the locally equivalent stat with the same variable name
const statName = getPropertyTitle(targetProp);
result.appendLog({
name: title,
value: `${statName}${operation === 'set' ? ' set to' : ''}` +
` ${value}`,
inline: true,
silenced: task.silent ?? false,
}, task.targetIds);
}
let damage, newValue, increment;
targetProp = await getSingleProperty(targetId, targetPropId);
if (!targetProp) return value;
if (operation === 'set') {
const total = targetProp.total || 0;
// Set represents what we want the value to be after damage
// So we need the actual damage to get to that value
damage = total - value;
// Damage can't exceed total value
if (damage > total && !targetProp.ignoreLowerLimit) damage = total;
// Damage must be positive
if (damage < 0 && !targetProp.ignoreUpperLimit) damage = 0;
newValue = targetProp.total - damage;
// Write the results
result.mutations.push({
targetIds: [targetId],
updates: [{
propId: targetProp._id,
set: { damage, value: newValue },
type: targetProp.type,
}],
contents: [{
name: title,
value: `${getPropertyTitle(targetProp)} set from ${targetProp.value} to ${value}`,
inline: true,
...task.silent && { silenced: true },
}]
});
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
} else if (operation === 'increment') {
const currentValue = targetProp.value || 0;
const currentDamage = targetProp.damage || 0;
increment = value;
// Can't increase damage above the remaining value
if (increment > currentValue && !targetProp.ignoreLowerLimit) increment = currentValue;
// Can't decrease damage below zero
if (-increment > currentDamage && !targetProp.ignoreUpperLimit) increment = -currentDamage;
damage = currentDamage + increment;
newValue = targetProp.total - damage;
// Write the results
result.mutations.push({
targetIds: [targetId],
updates: [{
propId: targetProp._id,
inc: { damage: increment, value: -increment },
type: targetProp.type,
}],
contents: [{
name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored',
value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`,
inline: true,
...task.silent && { silenced: true },
}]
});
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
}
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.after', userInput);
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.afterChildren', userInput);
return increment;
}
// Update the scope with the attribute, but updated to the new value
// TODO ideally we re-write the getEffectiveActionScope code to be more
// getSomethingFromScope which does the same work, but for a single key, and includes all
// updates to the doc returned that are already applied in the result array
function setScope(result, targetProp, newValue, damage) {
// This isn't the defining property, don't bother
if (targetProp.overridden) return;
const key = targetProp.variableName;
// No variable name can't set scope
if (!key) return;
// Make sure scope is defined
if (!result.scope) result.scope = {};
result.scope[key] = {
...EJSON.clone(targetProp),
value: newValue,
damage,
};
}