Support target-scoped trauma mechanics

This commit is contained in:
JB
2026-07-27 22:20:52 -04:00
parent 7e78d75f85
commit 0b3be3587b
20 changed files with 1032 additions and 93 deletions
+43 -14
View File
@@ -174,20 +174,43 @@ function requiredNumber(value: DatabaseValue, path: string, errors: string[]) {
return result ?? 0;
}
function isEntitySelector(value: unknown) {
if (!object(value) || typeof value.kind !== 'string') return false;
if (value.kind === 'agent' || value.kind === 'event_actor' || value.kind === 'event_target') {
return true;
}
return value.kind === 'event_field'
&& typeof value.path === 'string'
&& Boolean(value.path.trim());
}
function isValueReference(value: unknown) {
if (!object(value) || typeof value.source !== 'string') return false;
if (value.source === 'event' || value.source === 'equipment') {
return typeof value.path === 'string' && Boolean(value.path.trim());
}
if (value.source === 'combat') {
return typeof value.path === 'string'
&& Boolean(value.path.trim())
&& (value.timing === undefined || value.timing === 'pre_event' || value.timing === 'current');
}
if (value.source === 'attribute') {
return typeof value.attributeId === 'string' && Boolean(value.attributeId.trim());
}
if (value.source === 'scenario') {
return typeof value.key === 'string' && Boolean(value.key.trim());
}
if (value.source === 'reducer') return true;
return value.source === 'pool'
&& typeof value.pool === 'string'
&& Boolean(value.pool.trim())
&& (value.entity === undefined || isEntitySelector(value.entity));
}
function isNumericExpression(value: unknown): value is NumericExpression {
if (finite(value)) return true;
if (!object(value)) return false;
if ('ref' in value) {
if (!object(value.ref) || typeof value.ref.source !== 'string') return false;
const source = value.ref.source;
if (source === 'event' || source === 'combat' || source === 'equipment') {
return typeof value.ref.path === 'string';
}
if (source === 'scenario') return typeof value.ref.key === 'string';
if (source === 'reducer') return true;
if (source === 'pool') return typeof value.ref.pool === 'string';
return false;
}
if ('ref' in value) return isValueReference(value.ref);
if (value.op === 'clamp') {
return isNumericExpression(value.value)
&& isNumericExpression(value.min)
@@ -233,14 +256,20 @@ function isReducer(value: unknown): value is Reducer {
function isMutation(value: unknown): value is ClauseMutation {
if (!object(value) || typeof value.kind !== 'string') return false;
if (value.kind === 'none') return true;
if (value.kind === 'clear') return typeof value.pool === 'string';
if (value.kind === 'clear') {
return typeof value.pool === 'string'
&& (value.entity === undefined || isEntitySelector(value.entity));
}
if (value.kind === 'transfer') {
return typeof value.pool === 'string' && object(value.from) && object(value.to)
return typeof value.pool === 'string'
&& isEntitySelector(value.from)
&& isEntitySelector(value.to)
&& (value.value === undefined || value.value === 'all' || isNumericExpression(value.value));
}
return ['add', 'set', 'consume', 'derive'].includes(value.kind)
&& typeof value.pool === 'string'
&& isNumericExpression(value.value);
&& isNumericExpression(value.value)
&& (value.entity === undefined || isEntitySelector(value.entity));
}
function parseScenarioInputs(