Adding skills & isac talent updates
This commit is contained in:
@@ -84,6 +84,7 @@ export type ConditionalEffectOutcomeRow = {
|
||||
combat_timing?: DatabaseValue;
|
||||
delivery_mode?: DatabaseValue;
|
||||
delivery_duration_seconds?: DatabaseValue;
|
||||
targeting_json?: DatabaseValue;
|
||||
value_scaling: DatabaseValue;
|
||||
unit: DatabaseValue;
|
||||
damage_basis: DatabaseValue;
|
||||
@@ -192,7 +193,8 @@ function isValueReference(value: unknown) {
|
||||
if (value.source === 'combat') {
|
||||
return typeof value.path === 'string'
|
||||
&& Boolean(value.path.trim())
|
||||
&& (value.timing === undefined || value.timing === 'pre_event' || value.timing === 'current');
|
||||
&& (value.timing === undefined || value.timing === 'pre_event' || value.timing === 'current')
|
||||
&& !(value.path === 'currentHealthPercent' && value.timing === 'pre_event');
|
||||
}
|
||||
if (value.source === 'attribute') {
|
||||
return typeof value.attributeId === 'string' && Boolean(value.attributeId.trim());
|
||||
@@ -211,6 +213,7 @@ function isNumericExpression(value: unknown): value is NumericExpression {
|
||||
if (finite(value)) return true;
|
||||
if (!object(value)) return false;
|
||||
if ('ref' in value) return isValueReference(value.ref);
|
||||
if (value.op === 'floor') return isNumericExpression(value.value);
|
||||
if (value.op === 'clamp') {
|
||||
return isNumericExpression(value.value)
|
||||
&& isNumericExpression(value.min)
|
||||
@@ -441,6 +444,7 @@ function parsePool(
|
||||
row: ConditionalEffectStatePoolRow,
|
||||
ruleId: string,
|
||||
index: number,
|
||||
selectedVariant: string,
|
||||
errors: string[]
|
||||
): { id: string; definition: PoolDefinition } | null {
|
||||
const path = `State pool ${index + 1}`;
|
||||
@@ -460,6 +464,14 @@ function parsePool(
|
||||
const duration = number(row.duration_seconds);
|
||||
const parameters = parseJson(row.parameters_json, `${path} parameters`, errors);
|
||||
if (parameters.ok && !object(parameters.value)) errors.push(`${path} parameters must be a JSON object.`);
|
||||
const durationVariants = parameters.ok && object(parameters.value)
|
||||
&& object(parameters.value.expiresAfterSecondsVariants)
|
||||
? parameters.value.expiresAfterSecondsVariants
|
||||
: null;
|
||||
const variantDuration = durationVariants ? number(durationVariants[selectedVariant] as DatabaseValue) : null;
|
||||
if (durationVariants && variantDuration === null) {
|
||||
errors.push(`${path} has no valid expiration duration for variant "${selectedVariant}".`);
|
||||
}
|
||||
return {
|
||||
id,
|
||||
definition: {
|
||||
@@ -470,7 +482,9 @@ function parsePool(
|
||||
...(minimum === null ? {} : { min: minimum }),
|
||||
max: maximum,
|
||||
...(bestCase === null ? {} : { bestCaseValue: bestCase }),
|
||||
...(duration === null ? {} : { expiresAfterSeconds: duration })
|
||||
...(variantDuration !== null
|
||||
? { expiresAfterSeconds: variantDuration }
|
||||
: duration === null ? {} : { expiresAfterSeconds: duration })
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -478,10 +492,14 @@ function parsePool(
|
||||
function outcomeKind(value: string): MechanicOutcome['kind'] | null {
|
||||
if (value === 'active_modifier') return 'attribute_modifier';
|
||||
if (value === 'direct_damage') return 'direct_damage';
|
||||
if (value === 'restoration' || ['restore_health', 'restore_armor', 'restore_ammo'].includes(value)) {
|
||||
if (
|
||||
value === 'restoration'
|
||||
|| ['restore_health', 'restore_armor', 'restore_ammo', 'restore_signature_ability_charge'].includes(value)
|
||||
) {
|
||||
return 'restoration';
|
||||
}
|
||||
if (value === 'resource_cost') return 'resource_cost';
|
||||
if (value === 'weapon_reload') return 'weapon_reload';
|
||||
if (value === 'marker') return 'marker';
|
||||
return null;
|
||||
}
|
||||
@@ -489,6 +507,7 @@ function outcomeKind(value: string): MechanicOutcome['kind'] | null {
|
||||
function outcomeScope(value: string): MechanicOutcome['scope'] | null {
|
||||
if (value === 'self') return 'self';
|
||||
if (value === 'equipped_weapon') return 'equipped_weapon';
|
||||
if (value === 'inactive_weapon') return 'inactive_weapon';
|
||||
if (value === 'target') return 'target';
|
||||
if (value === 'allies') return 'allies';
|
||||
if (value === 'self_and_allies') return 'self_and_allies';
|
||||
@@ -547,15 +566,17 @@ function parseOutcome(
|
||||
? 'armor'
|
||||
: text(row.application_kind) === 'restore_ammo'
|
||||
? 'ammo'
|
||||
: text(row.application_kind) === 'restore_signature_ability_charge'
|
||||
? 'signature_ability_charge'
|
||||
: text(row.application_kind) === 'resource_cost'
|
||||
? 'health'
|
||||
: null;
|
||||
const resource = nullableText(row.resource_kind) ?? legacyResource;
|
||||
if (
|
||||
(kind === 'restoration' || kind === 'resource_cost')
|
||||
&& !['health', 'armor', 'ammo'].includes(String(resource))
|
||||
&& !['health', 'armor', 'ammo', 'signature_ability_charge'].includes(String(resource))
|
||||
) {
|
||||
errors.push(`${path} requires an explicit health, armor, or ammo resource.`);
|
||||
errors.push(`${path} requires an explicit supported resource.`);
|
||||
}
|
||||
const combatTiming = nullableText(row.combat_timing);
|
||||
if (combatTiming && combatTiming !== 'pre_event' && combatTiming !== 'current') {
|
||||
@@ -572,6 +593,13 @@ function parseOutcome(
|
||||
if (delivery === 'instant' && durationSeconds !== null) {
|
||||
errors.push(`${path} instant delivery cannot set a duration.`);
|
||||
}
|
||||
let targeting: MechanicOutcome['targeting'] | undefined;
|
||||
if (row.targeting_json !== null && row.targeting_json !== undefined && row.targeting_json !== '') {
|
||||
const parsed = parseJson(row.targeting_json, `${path} targeting`, errors);
|
||||
if (parsed.ok && typeof parsed.value === 'object' && parsed.value !== null) {
|
||||
targeting = parsed.value as MechanicOutcome['targeting'];
|
||||
}
|
||||
}
|
||||
return {
|
||||
clauseId: nullableText(row.clause_id),
|
||||
outcome: {
|
||||
@@ -591,6 +619,7 @@ function parseOutcome(
|
||||
...(delivery === 'continuous' && durationSeconds !== null
|
||||
? { delivery, durationSeconds }
|
||||
: {}),
|
||||
...(targeting ? { targeting } : {}),
|
||||
...(kind === 'attribute_modifier' && nullableText(row.stat_attribute_id)
|
||||
? { attributeId: text(row.stat_attribute_id) }
|
||||
: {}),
|
||||
@@ -627,16 +656,6 @@ export function adaptConditionalEffectProgram(
|
||||
}
|
||||
const scenarioInputs = parseScenarioInputs(input.rule.scenario_inputs_json, errors);
|
||||
|
||||
const parsedPools = input.statePools
|
||||
.slice()
|
||||
.sort((left, right) => (number(left.sort_order) ?? 0) - (number(right.sort_order) ?? 0))
|
||||
.map((row, index) => parsePool(row, ruleId, index, errors))
|
||||
.filter((value): value is NonNullable<typeof value> => value !== null);
|
||||
const poolKeys = new Map(parsedPools.map((pool) => [pool.id, pool.definition.key]));
|
||||
const triggerMap = new Map(
|
||||
input.triggers.map((trigger) => [text(trigger.id), trigger])
|
||||
);
|
||||
|
||||
const variantKeys = [...new Set(input.outcomes.map((row) => text(row.variant_key)).filter(Boolean))];
|
||||
let selectedVariant = options.variantKey?.trim() || '';
|
||||
if (!selectedVariant) {
|
||||
@@ -648,6 +667,15 @@ export function adaptConditionalEffectProgram(
|
||||
} else if (variantKeys.length && !variantKeys.includes(selectedVariant)) {
|
||||
errors.push(`Variant "${selectedVariant}" does not exist for this rule.`);
|
||||
}
|
||||
const parsedPools = input.statePools
|
||||
.slice()
|
||||
.sort((left, right) => (number(left.sort_order) ?? 0) - (number(right.sort_order) ?? 0))
|
||||
.map((row, index) => parsePool(row, ruleId, index, selectedVariant, errors))
|
||||
.filter((value): value is NonNullable<typeof value> => value !== null);
|
||||
const poolKeys = new Map(parsedPools.map((pool) => [pool.id, pool.definition.key]));
|
||||
const triggerMap = new Map(
|
||||
input.triggers.map((trigger) => [text(trigger.id), trigger])
|
||||
);
|
||||
|
||||
const selectedOutcomes = input.outcomes
|
||||
.filter((row) => text(row.variant_key) === selectedVariant)
|
||||
|
||||
Reference in New Issue
Block a user