Support target-scoped trauma mechanics
This commit is contained in:
@@ -235,6 +235,7 @@
|
||||
| 'static'
|
||||
| 'event'
|
||||
| 'attribute'
|
||||
| 'attribute_value'
|
||||
| 'reducer'
|
||||
| 'scenario'
|
||||
| 'protocol_upgrades'
|
||||
@@ -243,6 +244,7 @@
|
||||
const valueModelOptions = $derived([
|
||||
{ value: 'static', label: 'STATIC' },
|
||||
{ value: 'event', label: 'EVENT FIELD' },
|
||||
{ value: 'attribute_value', label: 'LIVE ATTRIBUTE' },
|
||||
{ value: 'attribute', label: 'ATTRIBUTE ÷ 100' },
|
||||
{ value: 'reducer', label: 'REDUCER' },
|
||||
{ value: 'scenario', label: 'SCENARIO' },
|
||||
@@ -298,6 +300,14 @@
|
||||
};
|
||||
}
|
||||
|
||||
function liveAttributeExpression(value: number, attributeId: string): NumericExpression {
|
||||
return {
|
||||
op: 'multiply',
|
||||
left: value,
|
||||
right: { ref: { source: 'attribute', attributeId } }
|
||||
};
|
||||
}
|
||||
|
||||
function outcomeValueModel(outcome: ConditionalMechanicOutcomeDraft): OutcomeValueModel {
|
||||
if (valueModelOverrides[outcome.key]) return valueModelOverrides[outcome.key];
|
||||
const expression = Object.values(outcome.variants)[0]?.expression;
|
||||
@@ -313,6 +323,7 @@
|
||||
if (expression.right.ref.source === 'event') return 'event';
|
||||
if (expression.right.ref.source === 'scenario') return 'scenario';
|
||||
if (expression.right.ref.source === 'reducer') return 'reducer';
|
||||
if (expression.right.ref.source === 'attribute') return 'attribute_value';
|
||||
}
|
||||
if (
|
||||
'op' in expression.right
|
||||
@@ -356,6 +367,15 @@
|
||||
|
||||
function outcomeAttributeReference(outcome: ConditionalMechanicOutcomeDraft) {
|
||||
const expression = Object.values(outcome.variants)[0]?.expression;
|
||||
if (
|
||||
expression
|
||||
&& typeof expression === 'object'
|
||||
&& 'op' in expression
|
||||
&& expression.op === 'multiply'
|
||||
&& typeof expression.right === 'object'
|
||||
&& 'ref' in expression.right
|
||||
&& expression.right.ref.source === 'attribute'
|
||||
) return expression.right.ref.attributeId;
|
||||
if (
|
||||
expression
|
||||
&& typeof expression === 'object'
|
||||
@@ -380,6 +400,9 @@
|
||||
if (model === 'attribute') return reference
|
||||
? attributePercentExpression(value, reference)
|
||||
: undefined;
|
||||
if (model === 'attribute_value') return reference
|
||||
? liveAttributeExpression(value, reference)
|
||||
: undefined;
|
||||
if (model === 'scenario') return reference
|
||||
? referenceExpression('scenario', reference, value)
|
||||
: undefined;
|
||||
@@ -398,7 +421,9 @@
|
||||
) {
|
||||
const outcome = rule.clauses[clauseIndex].outcomes[outcomeIndex];
|
||||
const nextOverrides = { ...valueModelOverrides };
|
||||
if (model === 'attribute' && !reference) nextOverrides[outcome.key] = 'attribute';
|
||||
if ((model === 'attribute' || model === 'attribute_value') && !reference) {
|
||||
nextOverrides[outcome.key] = model;
|
||||
}
|
||||
else delete nextOverrides[outcome.key];
|
||||
valueModelOverrides = nextOverrides;
|
||||
const variants = Object.fromEntries(Object.entries(outcome.variants).map(([key, variant]) => {
|
||||
@@ -442,6 +467,17 @@
|
||||
variants: { base: { value, expression } }
|
||||
}));
|
||||
}
|
||||
if (model === 'attribute_value') {
|
||||
return liveAttributeExpression(value, outcomeAttributeReference({
|
||||
key: 'temporary',
|
||||
label: 'Temporary',
|
||||
phase: 'on_event',
|
||||
kind: 'marker',
|
||||
scope: 'self',
|
||||
basis: 'flat',
|
||||
variants: { base: { value, expression } }
|
||||
}));
|
||||
}
|
||||
if (model === 'reducer') {
|
||||
return { op: 'multiply', left: value, right: { ref: { source: 'reducer' } } };
|
||||
}
|
||||
@@ -452,8 +488,11 @@
|
||||
function addMutation(clauseIndex: number) {
|
||||
const pool = rule.pools[0]?.key;
|
||||
if (!pool) return;
|
||||
const entity = rule.pools[0]?.scope === 'target'
|
||||
? { kind: 'event_target' as const }
|
||||
: undefined;
|
||||
updateClause(clauseIndex, {
|
||||
mutations: [...rule.clauses[clauseIndex].mutations, { kind: 'add', pool, value: 1 }]
|
||||
mutations: [...rule.clauses[clauseIndex].mutations, { kind: 'add', pool, value: 1, entity }]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -461,9 +500,25 @@
|
||||
const mutations = [...rule.clauses[clauseIndex].mutations];
|
||||
const current = mutations[mutationIndex];
|
||||
const nextPool = pool ?? ('pool' in current ? current.pool : rule.pools[0]?.key ?? '');
|
||||
const nextPoolDefinition = rule.pools.find((candidate) => candidate.key === nextPool);
|
||||
const entity = nextPoolDefinition?.scope === 'target'
|
||||
? ('entity' in current && current.entity ? current.entity : { kind: 'event_target' as const })
|
||||
: undefined;
|
||||
mutations[mutationIndex] = kind === 'clear'
|
||||
? { kind, pool: nextPool }
|
||||
: { kind, pool: nextPool, value: value ?? ('value' in current && typeof current.value === 'number' ? current.value : 1) };
|
||||
? { kind, pool: nextPool, entity }
|
||||
: { kind, pool: nextPool, value: value ?? ('value' in current && typeof current.value === 'number' ? current.value : 1), entity };
|
||||
updateClause(clauseIndex, { mutations });
|
||||
}
|
||||
|
||||
function updateMutationEntity(
|
||||
clauseIndex: number,
|
||||
mutationIndex: number,
|
||||
kind: 'event_target' | 'event_actor'
|
||||
) {
|
||||
const mutations = [...rule.clauses[clauseIndex].mutations];
|
||||
const mutation = mutations[mutationIndex];
|
||||
if (!('pool' in mutation) || mutation.kind === 'transfer') return;
|
||||
mutations[mutationIndex] = { ...mutation, entity: { kind } };
|
||||
updateClause(clauseIndex, { mutations });
|
||||
}
|
||||
|
||||
@@ -680,6 +735,15 @@
|
||||
{#if mutation.kind !== 'clear'}
|
||||
<label class="field">VALUE<input type="number" step="any" value={'value' in mutation && typeof mutation.value === 'number' ? mutation.value : 1} oninput={(event) => updateMutation(clauseIndex,mutationIndex,mutation.kind === 'set' || mutation.kind === 'consume' ? mutation.kind : 'add',undefined,Number(event.currentTarget.value))}/></label>
|
||||
{/if}
|
||||
{#if 'pool' in mutation && rule.pools.find((pool) => pool.key === mutation.pool)?.scope === 'target'}
|
||||
<SegmentedChoice
|
||||
legend="STATE OWNER"
|
||||
name={`${idPrefix}-mutation-entity-${clauseIndex}-${mutationIndex}`}
|
||||
options={[{value:'event_target',label:'EVENT TARGET'},{value:'event_actor',label:'EVENT ACTOR'}]}
|
||||
value={'entity' in mutation && mutation.entity?.kind === 'event_actor' ? 'event_actor' : 'event_target'}
|
||||
onchange={(values) => updateMutationEntity(clauseIndex,mutationIndex,values[0] as 'event_target'|'event_actor')}
|
||||
/>
|
||||
{/if}
|
||||
<button type="button" class="remove" onclick={() => updateClause(clauseIndex,{mutations:clause.mutations.filter((_,index) => index !== mutationIndex)})}>REMOVE</button>
|
||||
</div>
|
||||
{:else}
|
||||
@@ -747,14 +811,14 @@
|
||||
columns={Math.min(4,rule.scenarioInputs.length)}
|
||||
onchange={(values) => setOutcomeValueModel(clauseIndex,outcomeIndex,'scenario',values[0])}
|
||||
/>
|
||||
{:else if outcomeValueModel(outcome) === 'attribute'}
|
||||
{:else if outcomeValueModel(outcome) === 'attribute' || outcomeValueModel(outcome) === 'attribute_value'}
|
||||
<SearchableAssignment
|
||||
items={attributes}
|
||||
mode="single"
|
||||
compact
|
||||
subjectLabel="VALUE ATTRIBUTE"
|
||||
value={outcomeAttributeReference(outcome)}
|
||||
onvaluechange={(value) => setOutcomeValueModel(clauseIndex,outcomeIndex,'attribute',value)}
|
||||
onvaluechange={(value) => setOutcomeValueModel(clauseIndex,outcomeIndex,outcomeValueModel(outcome),value)}
|
||||
/>
|
||||
{/if}
|
||||
{#if rule.pools.length}
|
||||
@@ -823,7 +887,7 @@
|
||||
.branch-readout{display:grid;align-content:center;gap:3px;min-height:44px;padding:7px 9px;border:1px solid var(--shd-border);background:var(--shd-surface-raised)}.branch-readout span{color:var(--shd-text-muted);font-size:var(--shd-type-telemetry);font-weight:900;letter-spacing:.7px}.branch-readout strong{font-size:var(--shd-type-supporting)}
|
||||
.reducer-controls{display:grid;grid-template-columns:minmax(0,1fr) repeat(2,minmax(130px,.25fr));align-items:end;gap:var(--shd-gap-sm);padding:0 var(--shd-gap-sm) var(--shd-gap-sm)}
|
||||
.mutation-panel{margin:0 var(--shd-gap-sm) var(--shd-gap-sm);border:1px solid var(--shd-border-soft)}
|
||||
.mutation-row{display:grid;grid-template-columns:minmax(300px,1fr) minmax(260px,1fr) minmax(110px,.35fr) auto;align-items:end;gap:8px;padding:8px;border-bottom:1px solid var(--shd-border-soft)}
|
||||
.mutation-row{display:grid;grid-template-columns:minmax(260px,1fr) minmax(220px,1fr) minmax(110px,.35fr) minmax(240px,1fr) auto;align-items:end;gap:8px;padding:8px;border-bottom:1px solid var(--shd-border-soft)}
|
||||
.outcome-card{display:grid;gap:8px;margin:var(--shd-gap-sm);padding:9px;border:1px solid var(--shd-border);border-left:2px solid var(--shd-info);background:var(--shd-surface-raised)}
|
||||
.outcome-grid{display:grid;grid-template-columns:minmax(200px,1fr) minmax(360px,1.4fr) minmax(120px,.3fr);align-items:end;gap:8px}
|
||||
.variant-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px}
|
||||
|
||||
Reference in New Issue
Block a user