121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
|
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
|
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
|
import { applyDefaultAfterPropTasks, applyTaskToEachTarget } from '/imports/api/engine/action/functions/applyTaskGroups';
|
|
import getPropertyTitle from '/imports/api/utility/getPropertyTitle';
|
|
import recalculateCalculation from '/imports/api/engine/action/functions/recalculateCalculation';
|
|
import { PropTask } from '/imports/api/engine/action/tasks/Task';
|
|
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
|
import { getVariables } from '/imports/api/engine/loadCreatures';
|
|
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
|
import { isFiniteNode } from '/imports/parser/parseTree/constant';
|
|
|
|
export default async function applySavingThrowProperty(
|
|
task: PropTask, action: EngineAction, result: TaskResult, inputProvider: InputProvider
|
|
): Promise<void> {
|
|
|
|
const prop = task.prop;
|
|
|
|
if (prop.type !== 'savingThrow') {
|
|
throw new Meteor.Error('wrong-property', `Expected a savingThrow, got ${prop.type} instead`);
|
|
}
|
|
|
|
const saveTargetIds = prop.target === 'self' ? [action.creatureId] : task.targetIds;
|
|
|
|
if (saveTargetIds.length > 1) {
|
|
return applyTaskToEachTarget(action, task, saveTargetIds, inputProvider);
|
|
}
|
|
|
|
if (prop.dc) {
|
|
recalculateCalculation(prop.dc, action, 'reduce', inputProvider);
|
|
}
|
|
|
|
if (!isFiniteNode(prop.dc?.valueNode)) {
|
|
result.appendLog({
|
|
name: 'Error',
|
|
value: 'Saving throw requires a DC',
|
|
silenced: prop.silent,
|
|
}, saveTargetIds);
|
|
return applyDefaultAfterPropTasks(action, prop, saveTargetIds, inputProvider);
|
|
}
|
|
|
|
const dc = Number(prop.dc?.value ?? 0);
|
|
result.appendLog({
|
|
name: getPropertyTitle(prop),
|
|
value: `DC **${dc}**`,
|
|
inline: true,
|
|
silenced: prop.silent,
|
|
}, saveTargetIds);
|
|
|
|
const targetId = saveTargetIds[0];
|
|
|
|
// If there are no save targets, apply all children as if the save both
|
|
// succeeded and failed
|
|
if (!targetId) {
|
|
result.pushScope = {
|
|
['~saveFailed']: { value: true },
|
|
['~saveSucceeded']: { value: true },
|
|
}
|
|
return applyDefaultAfterPropTasks(action, prop, saveTargetIds, inputProvider);
|
|
}
|
|
|
|
// Each target makes the saving throw
|
|
const save = prop.stat ? getFromScope(prop.stat, getVariables(targetId)) : undefined;
|
|
|
|
if (!save) {
|
|
result.appendLog({
|
|
name: 'Saving throw error',
|
|
value: 'No saving throw found: ' + prop.stat,
|
|
silenced: prop.silent,
|
|
}, [targetId]);
|
|
return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
|
}
|
|
|
|
const rollModifierText = numberToSignedString(save.value, true);
|
|
const rollModifier = save.value;
|
|
|
|
let value, resultPrefix;
|
|
if (save.advantage === 1) {
|
|
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
|
if (a >= b) {
|
|
value = a;
|
|
resultPrefix = `Advantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
|
} else {
|
|
value = b;
|
|
resultPrefix = `Advantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
|
}
|
|
} else if (save.advantage === -1) {
|
|
const [[a, b]] = await inputProvider.rollDice([{ number: 2, diceSize: 20 }]);
|
|
if (a <= b) {
|
|
value = a;
|
|
resultPrefix = `Disadvantage\n1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText}`;
|
|
} else {
|
|
value = b;
|
|
resultPrefix = `Disadvantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
|
|
}
|
|
} else {
|
|
const [[rolledValue]] = await inputProvider.rollDice([{ number: 1, diceSize: 20 }]);
|
|
value = rolledValue;
|
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText}`
|
|
}
|
|
result.pushScope = {};
|
|
result.pushScope['~saveDiceRoll'] = { value };
|
|
const resultValue = value + rollModifier || 0;
|
|
result.pushScope['~saveRoll'] = { value: resultValue };
|
|
const saveSuccess = resultValue >= dc;
|
|
if (saveSuccess) {
|
|
result.pushScope['~saveSucceeded'] = { value: true };
|
|
result.pushScope['~saveFailed'] = { value: false };
|
|
} else {
|
|
result.pushScope['~saveFailed'] = { value: true };
|
|
result.pushScope['~saveSucceeded'] = { value: false };
|
|
}
|
|
result.appendLog({
|
|
name: saveSuccess ? 'Successful save' : 'Failed save',
|
|
value: resultPrefix + '\n**' + resultValue + '**',
|
|
inline: true,
|
|
silenced: prop.silent,
|
|
}, [targetId]);
|
|
return applyDefaultAfterPropTasks(action, prop, [targetId], inputProvider);
|
|
}
|