feat: add source-owned combat mechanic programs

This commit is contained in:
JB
2026-07-27 13:35:00 -04:00
parent 5a67b252aa
commit 4eb754e7c8
62 changed files with 14815 additions and 905 deletions
+42 -7
View File
@@ -13,6 +13,10 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
talentTiers,
talentEffects,
talentTemplates,
conditionalRules,
conditionalPools,
conditionalClauses,
conditionalOutcomes,
chipSets,
chipTwoPiece,
chipTalents,
@@ -26,6 +30,7 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
specializations,
specializationFocuses,
specializationAbilities,
specializationAbilityAttacks,
focusAbilities,
specializationNodes,
specializationRanks,
@@ -35,7 +40,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
specializationCombatModifiers,
activeCalculationRevision,
calculationStages,
calculationProfiles
calculationProfiles,
skillCalculationProfiles
] = await Promise.all([
(await database.prepare('SELECT * FROM stat_attributes WHERE active=1 ORDER BY name COLLATE NOCASE')).all(),
(await database.prepare('SELECT * FROM gear WHERE active=1 ORDER BY slot,name COLLATE NOCASE')).all(),
@@ -48,6 +54,10 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
(await database.prepare('SELECT * FROM talent_tiers ORDER BY tier')).all(),
(await database.prepare('SELECT * FROM talent_tier_effects')).all(),
(await database.prepare('SELECT * FROM talent_description_templates')).all(),
(await database.prepare('SELECT * FROM conditional_effect_rules WHERE configured=1')).all(),
(await database.prepare('SELECT * FROM conditional_effect_state_pools ORDER BY rule_id,sort_order')).all(),
(await database.prepare('SELECT * FROM conditional_effect_clauses WHERE active=1 ORDER BY rule_id,sort_order')).all(),
(await database.prepare('SELECT * FROM conditional_effect_outcomes ORDER BY rule_id,variant_key,outcome_key,sort_order')).all(),
(await database.prepare('SELECT * FROM skill_chip_sets WHERE active=1 ORDER BY name COLLATE NOCASE')).all(),
(await database.prepare('SELECT * FROM skill_chip_two_piece_bonuses')).all(),
(await database.prepare('SELECT * FROM skill_chip_talents')).all(),
@@ -61,6 +71,7 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
(await database.prepare('SELECT * FROM specializations WHERE active=1 ORDER BY name COLLATE NOCASE')).all(),
(await database.prepare('SELECT * FROM specialization_focuses WHERE active=1 ORDER BY specialization_id,display_order,name COLLATE NOCASE')).all(),
(await database.prepare('SELECT * FROM specialization_abilities WHERE active=1 ORDER BY specialization_id,name COLLATE NOCASE')).all(),
(await database.prepare('SELECT * FROM specialization_ability_attacks')).all(),
(await database.prepare('SELECT * FROM specialization_focus_abilities ORDER BY focus_id,ability_slot')).all(),
(await database.prepare('SELECT * FROM specialization_tree_nodes WHERE active=1 ORDER BY focus_id,row_number,column_number')).all(),
(await database.prepare('SELECT * FROM specialization_tree_node_ranks ORDER BY node_id,rank')).all(),
@@ -73,10 +84,23 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
JOIN calculation_revisions revision ON revision.id=stage.revision_id
WHERE revision.status='active' ORDER BY stage.display_order`)).all(),
(await database.prepare(`SELECT profile.* FROM attribute_calculation_profiles profile
JOIN calculation_revisions revision ON revision.id=profile.revision_id
WHERE revision.status='active' ORDER BY profile.stage_key,profile.stat_attribute_id`)).all(),
(await database.prepare(`SELECT profile.* FROM skill_attribute_calculation_profiles profile
JOIN calculation_revisions revision ON revision.id=profile.revision_id
WHERE revision.status='active' ORDER BY profile.stage_key,profile.stat_attribute_id`)).all()
]);
const hydrateRule = (sourceType: string, sourceId: unknown) => {
const rule = conditionalRules.find((item) => item.source_type === sourceType && item.source_id === sourceId);
return rule ? {
rule,
statePools: conditionalPools.filter((pool) => pool.rule_id === rule.id),
clauses: conditionalClauses.filter((clause) => clause.rule_id === rule.id),
outcomes: conditionalOutcomes.filter((outcome) => outcome.rule_id === rule.id),
triggers: []
} : null;
};
const configuredTalents = talents.map((talent) => ({
...talent,
template: talentTemplates.find((item) => item.talent_id === talent.id)?.template ?? '',
@@ -85,7 +109,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
.map((tier) => ({
...tier,
effects: talentEffects.filter((effect) => effect.talent_tier_id === tier.id)
}))
})),
rule: hydrateRule('talent', talent.id)
}));
return {
@@ -109,7 +134,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
talent: talent
? {
...talent,
effects: chipTalentEffects.filter((effect) => effect.talent_id === talent.id)
effects: chipTalentEffects.filter((effect) => effect.talent_id === talent.id),
rule: hydrateRule('skill_chip_talent', talent.id)
}
: null
};
@@ -130,7 +156,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
talent: talent
? {
...talent,
effects: protocolTalentEffects.filter((effect) => effect.talent_id === talent.id)
effects: protocolTalentEffects.filter((effect) => effect.talent_id === talent.id),
rule: hydrateRule('os_protocol_talent', talent.id)
}
: null
};
@@ -143,7 +170,13 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
const abilities = focusAbilities
.filter((link) => link.focus_id === focus.id)
.sort((left, right) => Number(left.ability_slot) - Number(right.ability_slot))
.map((link) => specializationAbilities.find((ability) => ability.id === link.ability_id))
.map((link) => {
const ability = specializationAbilities.find((entry) => entry.id === link.ability_id);
return ability ? {
...ability,
attack: specializationAbilityAttacks.find((attack) => attack.ability_id === ability.id) ?? null
} : null;
})
.filter(Boolean);
const nodes = specializationNodes
.filter((node) => node.focus_id === focus.id)
@@ -171,7 +204,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
),
modifiers: specializationCombatModifiers.filter(
(modifier) => modifier.rank_combat_effect_id === effect.id
)
),
rule: hydrateRule('specialization_tree_rank', effect.id)
}))
}))
}));
@@ -181,7 +215,8 @@ export async function loadSharedBuildCombatCatalog(database: Database) {
calculation: {
revision: activeCalculationRevision ?? null,
stages: calculationStages,
profiles: calculationProfiles
profiles: calculationProfiles,
skillProfiles: skillCalculationProfiles
}
};
}