Adding skills & isac talent updates

This commit is contained in:
jb
2026-07-29 20:56:22 -04:00
parent f1b473ffa8
commit 65a4987cad
68 changed files with 5486 additions and 569 deletions
+225 -5
View File
@@ -644,7 +644,8 @@ if (conditionalRuleColumns.some((column) => column.name === 'trigger_id')) {
variant_key TEXT NOT NULL DEFAULT 'base',
outcome_key TEXT NOT NULL DEFAULT 'active',
application_kind TEXT NOT NULL DEFAULT 'active_modifier' CHECK(application_kind IN (
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo','resource_cost'
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo',
'restore_signature_ability_charge','resource_cost'
)),
effect_scope TEXT NOT NULL DEFAULT 'self' CHECK(effect_scope IN (
'self','equipped_weapon','target','allies','self_and_allies','assigned_ability','all_skills'
@@ -674,7 +675,9 @@ if (conditionalRuleColumns.some((column) => column.name === 'trigger_id')) {
CHECK(
(application_kind='active_modifier' AND stat_attribute_id IS NOT NULL AND damage_basis IS NULL)
OR (application_kind='direct_damage' AND damage_basis IS NOT NULL)
OR application_kind IN ('restore_health','restore_armor','restore_ammo','resource_cost')
OR application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge','resource_cost'
)
)
)`);
await database.exec(`INSERT INTO conditional_effect_rules
@@ -1205,7 +1208,8 @@ if (sourceOwnedClauseColumns.some((column) => column.name === 'trigger_id')) {
variant_key TEXT NOT NULL DEFAULT 'base',
outcome_key TEXT NOT NULL DEFAULT 'active',
application_kind TEXT NOT NULL DEFAULT 'active_modifier' CHECK(application_kind IN (
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo','resource_cost','marker'
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo',
'restore_signature_ability_charge','resource_cost','marker'
)),
effect_scope TEXT NOT NULL DEFAULT 'self' CHECK(effect_scope IN (
'self','equipped_weapon','target','allies','self_and_allies','assigned_ability','all_skills'
@@ -1223,7 +1227,9 @@ if (sourceOwnedClauseColumns.some((column) => column.name === 'trigger_id')) {
'while_active','on_event','after_event','on_enter','on_exit'
)),
combat_timing TEXT NOT NULL DEFAULT 'current' CHECK(combat_timing IN ('pre_event','current')),
resource_kind TEXT CHECK(resource_kind IS NULL OR resource_kind IN ('health','armor','ammo')),
resource_kind TEXT CHECK(resource_kind IS NULL OR resource_kind IN (
'health','armor','ammo','signature_ability_charge'
)),
min_state_value REAL,
max_state_value REAL,
description_slot INTEGER CHECK(description_slot IS NULL OR description_slot>=1),
@@ -1237,7 +1243,10 @@ if (sourceOwnedClauseColumns.some((column) => column.name === 'trigger_id')) {
CHECK(
(application_kind='active_modifier' AND stat_attribute_id IS NOT NULL AND damage_basis IS NULL)
OR (application_kind='direct_damage' AND damage_basis IS NOT NULL)
OR application_kind IN ('restore_health','restore_armor','restore_ammo','resource_cost','marker')
OR application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge',
'resource_cost','marker'
)
)
)`);
await database.exec(`INSERT INTO conditional_effect_outcomes
@@ -1253,6 +1262,7 @@ if (sourceOwnedClauseColumns.some((column) => column.name === 'trigger_id')) {
WHEN 'restore_health' THEN 'health'
WHEN 'restore_armor' THEN 'armor'
WHEN 'restore_ammo' THEN 'ammo'
WHEN 'restore_signature_ability_charge' THEN 'signature_ability_charge'
ELSE NULL END,
min_state_value,max_state_value,description_slot,sort_order,created_at,updated_at
FROM conditional_effect_outcomes_triggered`);
@@ -2127,6 +2137,216 @@ if (skillChipAbilityColumn && Number(skillChipAbilityColumn.notnull) !== 1) {
await database.exec('CREATE UNIQUE INDEX IF NOT EXISTS skill_chip_sets_name_nocase_idx ON skill_chip_sets(name COLLATE NOCASE)');
await database.exec('CREATE INDEX IF NOT EXISTS skill_chip_sets_ability_idx ON skill_chip_sets(specialization_ability_id,active)');
const conditionalOutcomeSchema = await (await database.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='conditional_effect_outcomes'"
)).get();
if (
conditionalOutcomeSchema
&& !String(conditionalOutcomeSchema.sql ?? '').includes('signature_ability_charge')
) {
console.log('Adding Signature Ability Charge as a conditional-mechanic resource.');
await database.exec('DROP INDEX IF EXISTS conditional_effect_outcomes_rule_idx');
await database.exec('PRAGMA foreign_keys=OFF');
await database.exec('BEGIN');
try {
await database.exec('DROP TABLE IF EXISTS conditional_effect_outcomes_signature_charge');
await database.exec(`CREATE TABLE conditional_effect_outcomes_signature_charge (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES conditional_effect_rules(id) ON DELETE CASCADE,
clause_id TEXT REFERENCES conditional_effect_clauses(id) ON DELETE CASCADE,
state_pool_id TEXT REFERENCES conditional_effect_state_pools(id) ON DELETE SET NULL,
effect_key TEXT NOT NULL,
variant_key TEXT NOT NULL DEFAULT 'base',
outcome_key TEXT NOT NULL DEFAULT 'active',
application_kind TEXT NOT NULL DEFAULT 'active_modifier' CHECK(application_kind IN (
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo',
'restore_signature_ability_charge','resource_cost','marker'
)),
effect_scope TEXT NOT NULL DEFAULT 'self' CHECK(effect_scope IN (
'self','equipped_weapon','target','allies','self_and_allies','assigned_ability','all_skills'
)),
stat_attribute_id TEXT REFERENCES stat_attributes(id),
value REAL NOT NULL,
value_expression_json TEXT,
value_basis TEXT NOT NULL DEFAULT 'flat' CHECK(value_basis IN (
'flat','max_health_percent','current_health_percent','weapon_base_percent'
)),
value_scaling TEXT NOT NULL DEFAULT 'per_state_unit' CHECK(value_scaling IN ('flat','per_state_unit')),
unit TEXT,
damage_basis TEXT CHECK(damage_basis IS NULL OR damage_basis IN ('flat','weapon_base_percent')),
application_phase TEXT NOT NULL DEFAULT 'while_active' CHECK(application_phase IN (
'while_active','on_event','after_event','on_enter','on_exit'
)),
combat_timing TEXT NOT NULL DEFAULT 'current' CHECK(combat_timing IN ('pre_event','current')),
resource_kind TEXT CHECK(resource_kind IS NULL OR resource_kind IN (
'health','armor','ammo','signature_ability_charge'
)),
delivery_mode TEXT NOT NULL DEFAULT 'instant' CHECK(delivery_mode IN ('instant','continuous')),
delivery_duration_seconds REAL,
min_state_value REAL,
max_state_value REAL,
description_slot INTEGER CHECK(description_slot IS NULL OR description_slot>=1),
sort_order INTEGER NOT NULL CHECK(sort_order>=1),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(rule_id,variant_key,outcome_key,sort_order),
CHECK(delivery_duration_seconds IS NULL OR delivery_duration_seconds>0),
CHECK(delivery_mode='instant' OR (
application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge'
)
AND delivery_duration_seconds IS NOT NULL
)),
CHECK(min_state_value IS NULL OR min_state_value>=0),
CHECK(max_state_value IS NULL OR max_state_value>=0),
CHECK(min_state_value IS NULL OR max_state_value IS NULL OR max_state_value>=min_state_value),
CHECK(
(application_kind='active_modifier' AND stat_attribute_id IS NOT NULL AND damage_basis IS NULL)
OR (application_kind='direct_damage' AND damage_basis IS NOT NULL)
OR application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge',
'resource_cost','marker'
)
)
)`);
await database.exec(`INSERT INTO conditional_effect_outcomes_signature_charge (
id,rule_id,clause_id,state_pool_id,effect_key,variant_key,outcome_key,
application_kind,effect_scope,stat_attribute_id,value,value_expression_json,
value_basis,value_scaling,unit,damage_basis,application_phase,combat_timing,
resource_kind,delivery_mode,delivery_duration_seconds,min_state_value,max_state_value,
description_slot,sort_order,created_at,updated_at
)
SELECT
id,rule_id,clause_id,state_pool_id,effect_key,variant_key,outcome_key,
application_kind,effect_scope,stat_attribute_id,value,value_expression_json,
value_basis,value_scaling,unit,damage_basis,application_phase,combat_timing,
resource_kind,delivery_mode,delivery_duration_seconds,min_state_value,max_state_value,
description_slot,sort_order,created_at,updated_at
FROM conditional_effect_outcomes`);
await database.exec('DROP TABLE conditional_effect_outcomes');
await database.exec(
'ALTER TABLE conditional_effect_outcomes_signature_charge RENAME TO conditional_effect_outcomes'
);
await database.exec('COMMIT');
} catch (error) {
await database.exec('ROLLBACK');
throw error;
} finally {
await database.exec('PRAGMA foreign_keys=ON');
}
await database.exec(`CREATE INDEX IF NOT EXISTS conditional_effect_outcomes_rule_idx
ON conditional_effect_outcomes(rule_id,variant_key,outcome_key,sort_order)`);
}
const reloadOutcomeSchema = await (await database.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='conditional_effect_outcomes'"
)).get();
if (
reloadOutcomeSchema
&& !String(reloadOutcomeSchema.sql ?? '').includes("'weapon_reload'")
) {
console.log('Adding inactive-weapon reload actions to conditional mechanics.');
await database.exec('DROP INDEX IF EXISTS conditional_effect_outcomes_rule_idx');
await database.exec('PRAGMA foreign_keys=OFF');
await database.exec('BEGIN');
try {
await database.exec('DROP TABLE IF EXISTS conditional_effect_outcomes_weapon_reload');
await database.exec(`CREATE TABLE conditional_effect_outcomes_weapon_reload (
id TEXT PRIMARY KEY,
rule_id TEXT NOT NULL REFERENCES conditional_effect_rules(id) ON DELETE CASCADE,
clause_id TEXT REFERENCES conditional_effect_clauses(id) ON DELETE CASCADE,
state_pool_id TEXT REFERENCES conditional_effect_state_pools(id) ON DELETE SET NULL,
effect_key TEXT NOT NULL,
variant_key TEXT NOT NULL DEFAULT 'base',
outcome_key TEXT NOT NULL DEFAULT 'active',
application_kind TEXT NOT NULL DEFAULT 'active_modifier' CHECK(application_kind IN (
'active_modifier','direct_damage','restore_health','restore_armor','restore_ammo',
'restore_signature_ability_charge','resource_cost','weapon_reload','marker'
)),
effect_scope TEXT NOT NULL DEFAULT 'self' CHECK(effect_scope IN (
'self','equipped_weapon','inactive_weapon','target','allies','self_and_allies',
'assigned_ability','all_skills'
)),
stat_attribute_id TEXT REFERENCES stat_attributes(id),
value REAL NOT NULL,
value_expression_json TEXT,
value_basis TEXT NOT NULL DEFAULT 'flat' CHECK(value_basis IN (
'flat','max_health_percent','current_health_percent','weapon_base_percent'
)),
value_scaling TEXT NOT NULL DEFAULT 'per_state_unit' CHECK(value_scaling IN ('flat','per_state_unit')),
unit TEXT,
damage_basis TEXT CHECK(damage_basis IS NULL OR damage_basis IN ('flat','weapon_base_percent')),
application_phase TEXT NOT NULL DEFAULT 'while_active' CHECK(application_phase IN (
'while_active','on_event','after_event','on_enter','on_exit'
)),
combat_timing TEXT NOT NULL DEFAULT 'current' CHECK(combat_timing IN ('pre_event','current')),
resource_kind TEXT CHECK(resource_kind IS NULL OR resource_kind IN (
'health','armor','ammo','signature_ability_charge'
)),
delivery_mode TEXT NOT NULL DEFAULT 'instant' CHECK(delivery_mode IN ('instant','continuous')),
delivery_duration_seconds REAL,
min_state_value REAL,
max_state_value REAL,
description_slot INTEGER CHECK(description_slot IS NULL OR description_slot>=1),
sort_order INTEGER NOT NULL CHECK(sort_order>=1),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(rule_id,variant_key,outcome_key,sort_order),
CHECK(delivery_duration_seconds IS NULL OR delivery_duration_seconds>0),
CHECK(delivery_mode='instant' OR (
application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge'
)
AND delivery_duration_seconds IS NOT NULL
)),
CHECK(min_state_value IS NULL OR min_state_value>=0),
CHECK(max_state_value IS NULL OR max_state_value>=0),
CHECK(min_state_value IS NULL OR max_state_value IS NULL OR max_state_value>=min_state_value),
CHECK(
(application_kind='active_modifier' AND stat_attribute_id IS NOT NULL AND damage_basis IS NULL)
OR (application_kind='direct_damage' AND damage_basis IS NOT NULL)
OR application_kind IN (
'restore_health','restore_armor','restore_ammo','restore_signature_ability_charge',
'resource_cost','weapon_reload','marker'
)
)
)`);
await database.exec(`INSERT INTO conditional_effect_outcomes_weapon_reload (
id,rule_id,clause_id,state_pool_id,effect_key,variant_key,outcome_key,
application_kind,effect_scope,stat_attribute_id,value,value_expression_json,
value_basis,value_scaling,unit,damage_basis,application_phase,combat_timing,
resource_kind,delivery_mode,delivery_duration_seconds,min_state_value,max_state_value,
description_slot,sort_order,created_at,updated_at
)
SELECT
id,rule_id,clause_id,state_pool_id,effect_key,variant_key,outcome_key,
application_kind,effect_scope,stat_attribute_id,value,value_expression_json,
value_basis,value_scaling,unit,damage_basis,application_phase,combat_timing,
resource_kind,delivery_mode,delivery_duration_seconds,min_state_value,max_state_value,
description_slot,sort_order,created_at,updated_at
FROM conditional_effect_outcomes`);
await database.exec('DROP TABLE conditional_effect_outcomes');
await database.exec(
'ALTER TABLE conditional_effect_outcomes_weapon_reload RENAME TO conditional_effect_outcomes'
);
await database.exec('COMMIT');
} catch (error) {
await database.exec('ROLLBACK');
throw error;
} finally {
await database.exec('PRAGMA foreign_keys=ON');
}
await database.exec(`CREATE INDEX IF NOT EXISTS conditional_effect_outcomes_rule_idx
ON conditional_effect_outcomes(rule_id,variant_key,outcome_key,sort_order)`);
}
const outcomeTargetingColumns = await (
await database.prepare("PRAGMA table_info('conditional_effect_outcomes')")
).all();
if (!outcomeTargetingColumns.some((column) => column.name === 'targeting_json')) {
await database.exec('ALTER TABLE conditional_effect_outcomes ADD COLUMN targeting_json TEXT');
}
const calibrationCases = await (await database.prepare(
'SELECT id,loadout_snapshot_json FROM calculation_calibration_cases'
)).all();