feat: add community publishing and news syndication
This commit is contained in:
@@ -2,174 +2,185 @@ import { connect } from '@tursodatabase/database';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { ConditionalMechanicDraftV1 } from '$lib/calculations/mechanic-editor';
|
||||
import {
|
||||
conditionalRuleDraftFromRows,
|
||||
loadConditionalRuleDraft,
|
||||
saveConditionalRuleInTransaction
|
||||
conditionalRuleDraftFromRows,
|
||||
loadConditionalRuleDraft,
|
||||
saveConditionalRuleInTransaction,
|
||||
} from './conditional-effects.js';
|
||||
|
||||
const rule = {
|
||||
id: 'rule-1',
|
||||
simulation_readiness: 'simulation_ready',
|
||||
scenario_inputs_json: '[]'
|
||||
id: 'rule-1',
|
||||
simulation_readiness: 'simulation_ready',
|
||||
scenario_inputs_json: '[]',
|
||||
};
|
||||
|
||||
const clause = {
|
||||
id: 'clause-1',
|
||||
rule_id: 'rule-1',
|
||||
clause_key: 'hit',
|
||||
label: 'Hit',
|
||||
event_kind: 'weapon_hit',
|
||||
conditions_json: '{"kind":"true"}',
|
||||
reducer_json: '{"kind":"none"}',
|
||||
mutations_json: '[]',
|
||||
sort_order: 1,
|
||||
active: 1
|
||||
id: 'clause-1',
|
||||
rule_id: 'rule-1',
|
||||
clause_key: 'hit',
|
||||
label: 'Hit',
|
||||
event_kind: 'weapon_hit',
|
||||
conditions_json: '{"kind":"true"}',
|
||||
reducer_json: '{"kind":"none"}',
|
||||
mutations_json: '[]',
|
||||
sort_order: 1,
|
||||
active: 1,
|
||||
};
|
||||
|
||||
const outcome = {
|
||||
rule_id: 'rule-1',
|
||||
clause_id: 'clause-1',
|
||||
effect_key: 'damage',
|
||||
outcome_key: 'Damage',
|
||||
application_kind: 'active_modifier',
|
||||
effect_scope: 'self',
|
||||
stat_attribute_id: 'weapon-damage',
|
||||
value_basis: 'flat',
|
||||
value_scaling: 'flat',
|
||||
damage_basis: null,
|
||||
application_phase: 'while_active',
|
||||
combat_timing: 'current',
|
||||
resource_kind: null,
|
||||
state_pool_id: null,
|
||||
min_state_value: null,
|
||||
max_state_value: null,
|
||||
description_slot: 1,
|
||||
value: 3,
|
||||
value_expression_json: null
|
||||
rule_id: 'rule-1',
|
||||
clause_id: 'clause-1',
|
||||
effect_key: 'damage',
|
||||
outcome_key: 'Damage',
|
||||
application_kind: 'active_modifier',
|
||||
effect_scope: 'self',
|
||||
stat_attribute_id: 'weapon-damage',
|
||||
value_basis: 'flat',
|
||||
value_scaling: 'flat',
|
||||
damage_basis: null,
|
||||
application_phase: 'while_active',
|
||||
combat_timing: 'current',
|
||||
resource_kind: null,
|
||||
state_pool_id: null,
|
||||
min_state_value: null,
|
||||
max_state_value: null,
|
||||
description_slot: 1,
|
||||
value: 3,
|
||||
value_expression_json: null,
|
||||
};
|
||||
|
||||
describe('conditional mechanic persistence adapter', () => {
|
||||
it('rejects divergent metadata across value variants', () => {
|
||||
expect(() => conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', application_phase: 'after_event', value: 5 }
|
||||
],
|
||||
['I', 'II']
|
||||
)).toThrow(/inconsistent application_phase metadata/);
|
||||
});
|
||||
it('rejects divergent metadata across value variants', () => {
|
||||
expect(() =>
|
||||
conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'II',
|
||||
application_phase: 'after_event',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
['I', 'II'],
|
||||
),
|
||||
).toThrow(/inconsistent application_phase metadata/);
|
||||
});
|
||||
|
||||
it('loads consistent value variants into one outcome', () => {
|
||||
const draft = conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', value: 5 }
|
||||
],
|
||||
['I', 'II']
|
||||
);
|
||||
it('loads consistent value variants into one outcome', () => {
|
||||
const draft = conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', value: 5 },
|
||||
],
|
||||
['I', 'II'],
|
||||
);
|
||||
|
||||
expect(draft?.clauses[0].outcomes[0].variants).toEqual({
|
||||
I: { value: 3 },
|
||||
II: { value: 5 }
|
||||
});
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('delivery');
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('durationSeconds');
|
||||
});
|
||||
expect(draft?.clauses[0].outcomes[0].variants).toEqual({
|
||||
I: { value: 3 },
|
||||
II: { value: 5 },
|
||||
});
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('delivery');
|
||||
expect(draft?.clauses[0].outcomes[0]).not.toHaveProperty('durationSeconds');
|
||||
});
|
||||
|
||||
it('loads continuous delivery metadata and normalizes legacy instant variants', () => {
|
||||
const continuous = conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'I'
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'II'
|
||||
}
|
||||
],
|
||||
['I', 'II']
|
||||
);
|
||||
expect(continuous?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3
|
||||
});
|
||||
it('loads continuous delivery metadata and normalizes legacy instant variants', () => {
|
||||
const continuous = conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'I',
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
application_kind: 'restore_health',
|
||||
stat_attribute_id: null,
|
||||
resource_kind: 'health',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
variant_key: 'II',
|
||||
},
|
||||
],
|
||||
['I', 'II'],
|
||||
);
|
||||
expect(continuous?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
});
|
||||
|
||||
expect(() => conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', delivery_mode: 'instant' }
|
||||
],
|
||||
['I', 'II']
|
||||
)).not.toThrow();
|
||||
});
|
||||
expect(() =>
|
||||
conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{ ...outcome, variant_key: 'I' },
|
||||
{ ...outcome, variant_key: 'II', delivery_mode: 'instant' },
|
||||
],
|
||||
['I', 'II'],
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects divergent continuous-delivery durations across variants', () => {
|
||||
expect(() => conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'I',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'II',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 4
|
||||
}
|
||||
],
|
||||
['I', 'II']
|
||||
)).toThrow(/inconsistent delivery_duration_seconds metadata/);
|
||||
});
|
||||
it('rejects divergent continuous-delivery durations across variants', () => {
|
||||
expect(() =>
|
||||
conditionalRuleDraftFromRows(
|
||||
rule,
|
||||
[],
|
||||
[clause],
|
||||
[
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'I',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 3,
|
||||
},
|
||||
{
|
||||
...outcome,
|
||||
variant_key: 'II',
|
||||
delivery_mode: 'continuous',
|
||||
delivery_duration_seconds: 4,
|
||||
},
|
||||
],
|
||||
['I', 'II'],
|
||||
),
|
||||
).toThrow(/inconsistent delivery_duration_seconds metadata/);
|
||||
});
|
||||
|
||||
it('round-trips continuous delivery and direct event predicates through normalized rows', async () => {
|
||||
const database = await connect(':memory:');
|
||||
await database.exec(`CREATE TABLE conditional_effect_rules (
|
||||
it('round-trips continuous delivery and direct event predicates through normalized rows', async () => {
|
||||
const database = await connect(':memory:');
|
||||
await database.exec(`CREATE TABLE conditional_effect_rules (
|
||||
id TEXT PRIMARY KEY, source_type TEXT, source_id TEXT, configured INTEGER,
|
||||
script_version INTEGER, simulation_readiness TEXT, scenario_inputs_json TEXT,
|
||||
created_at TEXT, updated_at TEXT, UNIQUE(source_type,source_id)
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_state_pools (
|
||||
await database.exec(`CREATE TABLE conditional_effect_state_pools (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, state_key TEXT, label TEXT, state_kind TEXT,
|
||||
state_scope TEXT, initial_value REAL, minimum_value REAL, maximum_value REAL,
|
||||
best_case_value REAL, duration_seconds REAL, expiry_policy TEXT, refresh_policy TEXT,
|
||||
parameters_json TEXT, sort_order INTEGER, created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_clauses (
|
||||
await database.exec(`CREATE TABLE conditional_effect_clauses (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, clause_key TEXT, label TEXT, event_kind TEXT,
|
||||
recipe_key TEXT, recipe_parameters_json TEXT, conditions_json TEXT, reducer_json TEXT,
|
||||
weight_expression_json TEXT, emission_kind TEXT, state_pool_id TEXT, mutations_json TEXT,
|
||||
cooldown_seconds REAL, output_branch TEXT, sort_order INTEGER, active INTEGER,
|
||||
created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
await database.exec(`CREATE TABLE conditional_effect_outcomes (
|
||||
await database.exec(`CREATE TABLE conditional_effect_outcomes (
|
||||
id TEXT PRIMARY KEY, rule_id TEXT, clause_id TEXT, state_pool_id TEXT, effect_key TEXT,
|
||||
variant_key TEXT, outcome_key TEXT, application_kind TEXT, effect_scope TEXT,
|
||||
stat_attribute_id TEXT, value REAL, value_expression_json TEXT, value_basis TEXT,
|
||||
@@ -179,206 +190,245 @@ describe('conditional mechanic persistence adapter', () => {
|
||||
description_slot INTEGER, sort_order INTEGER, created_at TEXT, updated_at TEXT
|
||||
)`);
|
||||
|
||||
await saveConditionalRuleInTransaction(database, 'talent', 'predatory-aura', {
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [{
|
||||
key: 'marked-target',
|
||||
label: 'Marked Target',
|
||||
stateKind: 'target_mark',
|
||||
scope: 'target',
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18
|
||||
}
|
||||
} as ConditionalMechanicDraftV1['pools'][number] & {
|
||||
expiresAfterSecondsVariants: Record<string, number>;
|
||||
}],
|
||||
clauses: [{
|
||||
key: 'weapon-kill',
|
||||
label: 'Weapon kill',
|
||||
event: 'enemy_killed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: { source: 'combat', path: 'currentHealthPercent', timing: 'current' },
|
||||
operator: 'lt',
|
||||
right: 75
|
||||
},
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1
|
||||
}],
|
||||
outcomes: [{
|
||||
key: 'restore-health',
|
||||
label: 'Restore Health',
|
||||
phase: 'after_event',
|
||||
kind: 'restoration',
|
||||
scope: 'self',
|
||||
resource: 'health',
|
||||
basis: 'flat',
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
targeting: { kind: 'within_radius', population: 'allies', radiusMeters: 40 },
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 }
|
||||
}
|
||||
}, {
|
||||
key: 'target-detonation',
|
||||
label: 'Target Detonation',
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
basis: 'flat',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
variants: {
|
||||
I: {
|
||||
value: 240,
|
||||
expression: {
|
||||
op: 'multiply',
|
||||
left: 240,
|
||||
right: {
|
||||
op: 'divide',
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e'
|
||||
}
|
||||
},
|
||||
right: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
II: {
|
||||
value: 336,
|
||||
expression: {
|
||||
op: 'multiply',
|
||||
left: 336,
|
||||
right: {
|
||||
op: 'divide',
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e'
|
||||
}
|
||||
},
|
||||
right: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
key: 'extra-health-state',
|
||||
label: 'Extra Health state',
|
||||
event: 'extra_health_changed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: { source: 'event', path: 'values.hasExtraHealth' },
|
||||
operator: 'eq',
|
||||
right: true
|
||||
},
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [],
|
||||
outcomes: []
|
||||
}]
|
||||
});
|
||||
await saveConditionalRuleInTransaction(
|
||||
database,
|
||||
'talent',
|
||||
'predatory-aura',
|
||||
{
|
||||
version: 1,
|
||||
simulationReadiness: 'ready',
|
||||
scenarioInputs: [],
|
||||
pools: [
|
||||
{
|
||||
key: 'marked-target',
|
||||
label: 'Marked Target',
|
||||
stateKind: 'target_mark',
|
||||
scope: 'target',
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18,
|
||||
},
|
||||
} as ConditionalMechanicDraftV1['pools'][number] & {
|
||||
expiresAfterSecondsVariants: Record<string, number>;
|
||||
},
|
||||
],
|
||||
clauses: [
|
||||
{
|
||||
key: 'weapon-kill',
|
||||
label: 'Weapon kill',
|
||||
event: 'enemy_killed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: {
|
||||
source: 'combat',
|
||||
path: 'currentHealthPercent',
|
||||
timing: 'current',
|
||||
},
|
||||
operator: 'lt',
|
||||
right: 75,
|
||||
},
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [
|
||||
{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
outcomes: [
|
||||
{
|
||||
key: 'restore-health',
|
||||
label: 'Restore Health',
|
||||
phase: 'after_event',
|
||||
kind: 'restoration',
|
||||
scope: 'self',
|
||||
resource: 'health',
|
||||
basis: 'flat',
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
targeting: {
|
||||
kind: 'within_radius',
|
||||
population: 'allies',
|
||||
radiusMeters: 40,
|
||||
},
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 },
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'target-detonation',
|
||||
label: 'Target Detonation',
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
basis: 'flat',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
variants: {
|
||||
I: {
|
||||
value: 240,
|
||||
expression: {
|
||||
op: 'multiply',
|
||||
left: 240,
|
||||
right: {
|
||||
op: 'divide',
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e',
|
||||
},
|
||||
},
|
||||
right: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
II: {
|
||||
value: 336,
|
||||
expression: {
|
||||
op: 'multiply',
|
||||
left: 336,
|
||||
right: {
|
||||
op: 'divide',
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e',
|
||||
},
|
||||
},
|
||||
right: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'extra-health-state',
|
||||
label: 'Extra Health state',
|
||||
event: 'extra_health_changed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: { source: 'event', path: 'values.hasExtraHealth' },
|
||||
operator: 'eq',
|
||||
right: true,
|
||||
},
|
||||
reducer: { kind: 'none' },
|
||||
mutations: [],
|
||||
outcomes: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const saved = await (await database.prepare(
|
||||
'SELECT delivery_mode,delivery_duration_seconds FROM conditional_effect_outcomes ORDER BY variant_key'
|
||||
)).all();
|
||||
expect(saved).toEqual([
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 },
|
||||
{ delivery_mode: 'instant', delivery_duration_seconds: null },
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 },
|
||||
{ delivery_mode: 'instant', delivery_duration_seconds: null }
|
||||
]);
|
||||
expect(await (await database.prepare(
|
||||
'SELECT duration_seconds,parameters_json FROM conditional_effect_state_pools'
|
||||
)).get()).toEqual({
|
||||
duration_seconds: null,
|
||||
parameters_json: JSON.stringify({
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18
|
||||
}
|
||||
})
|
||||
});
|
||||
const loaded = await loadConditionalRuleDraft(
|
||||
database,
|
||||
'talent',
|
||||
'predatory-aura',
|
||||
['I', 'II']
|
||||
);
|
||||
expect(loaded?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
targeting: { kind: 'within_radius', population: 'allies', radiusMeters: 40 },
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 }
|
||||
}
|
||||
});
|
||||
expect(loaded?.clauses[0].if).toEqual({
|
||||
kind: 'compare',
|
||||
left: { source: 'combat', path: 'currentHealthPercent', timing: 'current' },
|
||||
operator: 'lt',
|
||||
right: 75
|
||||
});
|
||||
expect(loaded?.pools[0]).toMatchObject({
|
||||
key: 'marked-target',
|
||||
stateKind: 'target_mark',
|
||||
scope: 'target',
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18
|
||||
}
|
||||
});
|
||||
expect(loaded?.clauses[0].mutations).toEqual([{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1
|
||||
}]);
|
||||
expect(loaded?.clauses[0].outcomes[1]).toMatchObject({
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
variants: {
|
||||
I: {
|
||||
value: 240,
|
||||
expression: expect.objectContaining({
|
||||
right: expect.objectContaining({
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
II: { value: 336 }
|
||||
}
|
||||
});
|
||||
expect(loaded?.clauses[1]).toMatchObject({
|
||||
event: 'extra_health_changed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: { source: 'event', path: 'values.hasExtraHealth' },
|
||||
operator: 'eq',
|
||||
right: true
|
||||
}
|
||||
});
|
||||
});
|
||||
const saved = await (
|
||||
await database.prepare(
|
||||
'SELECT delivery_mode,delivery_duration_seconds FROM conditional_effect_outcomes ORDER BY variant_key',
|
||||
)
|
||||
).all();
|
||||
expect(saved).toEqual([
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 },
|
||||
{ delivery_mode: 'instant', delivery_duration_seconds: null },
|
||||
{ delivery_mode: 'continuous', delivery_duration_seconds: 3 },
|
||||
{ delivery_mode: 'instant', delivery_duration_seconds: null },
|
||||
]);
|
||||
expect(
|
||||
await (
|
||||
await database.prepare(
|
||||
'SELECT duration_seconds,parameters_json FROM conditional_effect_state_pools',
|
||||
)
|
||||
).get(),
|
||||
).toEqual({
|
||||
duration_seconds: null,
|
||||
parameters_json: JSON.stringify({
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const loaded = await loadConditionalRuleDraft(
|
||||
database,
|
||||
'talent',
|
||||
'predatory-aura',
|
||||
['I', 'II'],
|
||||
);
|
||||
expect(loaded?.clauses[0].outcomes[0]).toMatchObject({
|
||||
delivery: 'continuous',
|
||||
durationSeconds: 3,
|
||||
targeting: {
|
||||
kind: 'within_radius',
|
||||
population: 'allies',
|
||||
radiusMeters: 40,
|
||||
},
|
||||
variants: {
|
||||
I: { value: 240 },
|
||||
II: { value: 336 },
|
||||
},
|
||||
});
|
||||
expect(loaded?.clauses[0].if).toEqual({
|
||||
kind: 'compare',
|
||||
left: {
|
||||
source: 'combat',
|
||||
path: 'currentHealthPercent',
|
||||
timing: 'current',
|
||||
},
|
||||
operator: 'lt',
|
||||
right: 75,
|
||||
});
|
||||
expect(loaded?.pools[0]).toMatchObject({
|
||||
key: 'marked-target',
|
||||
stateKind: 'target_mark',
|
||||
scope: 'target',
|
||||
expiresAfterSecondsVariants: {
|
||||
I: 12,
|
||||
II: 18,
|
||||
},
|
||||
});
|
||||
expect(loaded?.clauses[0].mutations).toEqual([
|
||||
{
|
||||
kind: 'set',
|
||||
pool: 'marked-target',
|
||||
entity: { kind: 'event_target' },
|
||||
value: 1,
|
||||
},
|
||||
]);
|
||||
expect(loaded?.clauses[0].outcomes[1]).toMatchObject({
|
||||
phase: 'on_exit',
|
||||
kind: 'direct_damage',
|
||||
scope: 'target',
|
||||
statePool: 'marked-target',
|
||||
perStack: true,
|
||||
variants: {
|
||||
I: {
|
||||
value: 240,
|
||||
expression: expect.objectContaining({
|
||||
right: expect.objectContaining({
|
||||
left: {
|
||||
ref: {
|
||||
source: 'attribute',
|
||||
attributeId: '82a95611-283b-4fd8-a6ed-3567a316f36e',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
II: { value: 336 },
|
||||
},
|
||||
});
|
||||
expect(loaded?.clauses[1]).toMatchObject({
|
||||
event: 'extra_health_changed',
|
||||
if: {
|
||||
kind: 'compare',
|
||||
left: { source: 'event', path: 'values.hasExtraHealth' },
|
||||
operator: 'eq',
|
||||
right: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user