feat: add source-owned combat mechanic programs
This commit is contained in:
@@ -6,6 +6,7 @@ import { getDatabase } from '$lib/server/database';
|
||||
import { getPagination, getRegistrySearch } from '$lib/server/pagination';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { MECHANIC_EVENT_REGISTRY, type MechanicEventKind } from '$lib/calculations/mechanic';
|
||||
|
||||
const number = (value: unknown) => {
|
||||
const numeric = Number(value);
|
||||
@@ -18,6 +19,19 @@ const gearIcon = (slot: unknown) => String(slot).toLowerCase().replaceAll(' ', '
|
||||
const weaponIcon = (category: unknown) => String(category).toLowerCase() as GameIconName;
|
||||
const iconUrl = (type: GameDataIconType, id: unknown, updatedAt: unknown) =>
|
||||
updatedAt ? gameDataIconUrl(type, String(id), updatedAt) : '';
|
||||
const parsedObject = (value: unknown): Record<string, unknown> => {
|
||||
try {
|
||||
const parsed = JSON.parse(String(value ?? '{}'));
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
||||
? parsed as Record<string, unknown>
|
||||
: {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
const humanized = (value: unknown) => String(value ?? '')
|
||||
.replaceAll('_', ' ')
|
||||
.replace(/\b\w/g, (character) => character.toUpperCase());
|
||||
|
||||
export const load: PageServerLoad = async ({ params, url }) => {
|
||||
const category = databaseCategory(params.category);
|
||||
@@ -330,6 +344,21 @@ export const load: PageServerLoad = async ({ params, url }) => {
|
||||
JOIN stat_attributes attributes ON attributes.id=effects.stat_attribute_id
|
||||
WHERE effects.brand_set_id IN (${rows.map(() => '?').join(',')}) ORDER BY effects.pieces_required`)).all(...rows.map((row) => row.id))
|
||||
: [];
|
||||
const talentMechanics = category.slug === 'talents' && rows.length
|
||||
? await (await database.prepare(`SELECT rules.source_id AS talent_id,rules.id AS rule_id,
|
||||
clauses.id AS clause_id,clauses.label AS clause_label,clauses.event_kind,
|
||||
clauses.conditions_json,clauses.reducer_json,clauses.cooldown_seconds,
|
||||
outcomes.variant_key,outcomes.outcome_key,outcomes.application_kind,outcomes.effect_scope,
|
||||
outcomes.value,outcomes.damage_basis,attributes.name AS attribute_name,attributes.unit AS attribute_unit,
|
||||
outcomes.sort_order
|
||||
FROM conditional_effect_rules rules
|
||||
JOIN conditional_effect_clauses clauses ON clauses.rule_id=rules.id AND clauses.active=1
|
||||
LEFT JOIN conditional_effect_outcomes outcomes ON outcomes.clause_id=clauses.id
|
||||
LEFT JOIN stat_attributes attributes ON attributes.id=outcomes.stat_attribute_id
|
||||
WHERE rules.source_type='talent' AND rules.configured=1
|
||||
AND rules.source_id IN (${rows.map(() => '?').join(',')})
|
||||
ORDER BY rules.source_id,clauses.sort_order,outcomes.outcome_key,outcomes.sort_order`)).all(...rows.map((row) => row.id))
|
||||
: [];
|
||||
|
||||
const items = rows.map((row) => {
|
||||
switch (category.slug) {
|
||||
@@ -491,6 +520,49 @@ export const load: PageServerLoad = async ({ params, url }) => {
|
||||
const isWeaponTalent = talentCategory === 'Weapon';
|
||||
const normalizedCategory = talentCategory === 'Vest' ? 'Body Armor' : talentCategory;
|
||||
const slotLabel = isWeaponTalent && row.weapon_slot ? `WEAPON ${row.weapon_slot}` : normalizedCategory.toUpperCase();
|
||||
const mechanics = talentMechanics.filter((mechanic) => mechanic.talent_id === row.id);
|
||||
const rule = mechanics[0];
|
||||
const groupedOutcomes = new Map<string, any>();
|
||||
for (const outcome of mechanics.filter((mechanic) => mechanic.variant_key)) {
|
||||
const key = [
|
||||
outcome.outcome_key,
|
||||
outcome.application_kind,
|
||||
outcome.effect_scope,
|
||||
outcome.attribute_name ?? '',
|
||||
outcome.damage_basis ?? ''
|
||||
].join('|');
|
||||
const current = groupedOutcomes.get(key) ?? {
|
||||
outcomeKey: String(outcome.outcome_key),
|
||||
application: String(outcome.application_kind),
|
||||
scope: String(outcome.effect_scope),
|
||||
attribute: String(outcome.attribute_name ?? ''),
|
||||
unit: outcome.attribute_unit,
|
||||
damageBasis: outcome.damage_basis,
|
||||
values: {}
|
||||
};
|
||||
current.values[String(outcome.variant_key)] = Number(outcome.value);
|
||||
groupedOutcomes.set(key, current);
|
||||
}
|
||||
const outcomeSummary = [...groupedOutcomes.values()].map((outcome) => {
|
||||
const subject = outcome.application === 'active_modifier'
|
||||
? outcome.attribute
|
||||
: outcome.application === 'direct_damage'
|
||||
? `Direct Damage · ${outcome.damageBasis === 'weapon_base_percent' ? 'Weapon Base' : 'Flat'}`
|
||||
: humanized(outcome.application);
|
||||
const values = ['I', 'II']
|
||||
.filter((variant) => Number.isFinite(outcome.values[variant]))
|
||||
.map((variant) => {
|
||||
const raw = Number(outcome.values[variant]);
|
||||
const prefix = outcome.application === 'active_modifier' && raw > 0 ? '+' : '';
|
||||
return `T${variant} ${prefix}${valueWithUnit(raw, outcome.unit)}`;
|
||||
})
|
||||
.join(' · ');
|
||||
return `${subject} · ${values}`;
|
||||
}).join(' / ');
|
||||
const activation = [...new Map(mechanics.map((mechanic) => [
|
||||
String(mechanic.clause_id),
|
||||
`${mechanic.clause_label || humanized(mechanic.event_kind)} · ${MECHANIC_EVENT_REGISTRY[mechanic.event_kind as MechanicEventKind]?.label ?? humanized(mechanic.event_kind)}`
|
||||
])).values()].join(' / ');
|
||||
return {
|
||||
id: String(row.id), name: String(row.name), kicker: `${slotLabel} TALENT`,
|
||||
iconUrl: iconUrl('talent',row.id,row.icon_updated_at),
|
||||
@@ -507,7 +579,11 @@ export const load: PageServerLoad = async ({ params, url }) => {
|
||||
description: row.tier_i ? String(row.tier_i) : 'Description is not available.',
|
||||
secondaryDescription: row.tier_ii ? String(row.tier_ii) : 'Description is not available.',
|
||||
descriptionStyle: 'data' as const,
|
||||
afterLines: row.cooldown_seconds ? [{ label: 'TALENT COOLDOWN', value: `${number(row.cooldown_seconds)} SECONDS` }] : []
|
||||
afterLines: [
|
||||
...(activation ? [{ label: 'ACTIVATES WHEN', value: activation }] : []),
|
||||
...(outcomeSummary ? [{ label: 'CALCULATION EFFECTS', value: outcomeSummary }] : []),
|
||||
...(row.cooldown_seconds ? [{ label: 'TALENT COOLDOWN', value: `${number(row.cooldown_seconds)} SECONDS` }] : [])
|
||||
]
|
||||
};
|
||||
}
|
||||
case 'brands': {
|
||||
|
||||
Reference in New Issue
Block a user