import { databaseCategory } from '$lib/database/categories'; import type { DatabaseFact, DatabaseItem } from '$lib/database/items'; import type { GameIconName } from '$lib/game-icons'; import { gearBaseStatRange, weaponBaseRange } from '$lib/builder/loadout'; import { gameDataIconUrl, type GameDataIconType } from '$lib/server/game-data-icons'; import { managedImageUrl } from '$lib/server/managed-images'; 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); return Number.isFinite(numeric) ? (Number.isInteger(numeric) ? String(numeric) : numeric.toFixed(2).replace(/\.?0+$/, '')) : '—'; }; const valueWithUnit = (value: unknown, unit: unknown) => `${number(value)}${unit ? (unit === '%' ? '%' : ` ${unit}`) : ''}`; const range = (min: unknown, max: unknown, unit: unknown = '') => `${valueWithUnit(min, unit)}–${valueWithUnit(max, unit)}`; const list = (value: unknown, fallback = 'NONE') => typeof value === 'string' && value ? value : fallback; const gearIcon = (slot: unknown) => String(slot).toLowerCase().replaceAll(' ', '_') as GameIconName; 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 => { try { const parsed = JSON.parse(String(value ?? '{}')); return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record : {}; } catch { return {}; } }; const humanized = (value: unknown) => String(value ?? '') .replaceAll('_', ' ') .replace(/\b\w/g, (character) => character.toUpperCase()); const talentCardItem = ( row: Record, mechanics: Array>, weaponSlotOverride?: number ): DatabaseItem => { const talentCategory = String(row.category); const isWeaponTalent = talentCategory === 'Weapon'; const normalizedCategory = talentCategory === 'Vest' ? 'Body Armor' : talentCategory; const weaponSlot = weaponSlotOverride ?? (row.weapon_slot ? Number(row.weapon_slot) : undefined); const slotLabel = isWeaponTalent && weaponSlot ? `WEAPON ${weaponSlot}` : normalizedCategory.toUpperCase(); 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(' / '); const afterLines: DatabaseFact[] = [ ...(activation ? [{ label: 'ACTIVATES WHEN', value: activation }] : []), ...(row.duration_seconds ? [{ label: 'EFFECT DURATION', value: `${number(row.duration_seconds)} SECONDS` }] : []), ...(Number(row.max_stacks) > 1 ? [{ label: 'MAXIMUM STACKS', value: String(row.max_stacks) }] : []), ...(row.cooldown_seconds ? [{ label: 'TALENT COOLDOWN', value: `${number(row.cooldown_seconds)} SECONDS` }] : []) ]; const filterKey = isWeaponTalent ? 'weaponTalent' : 'talent'; const destination = isWeaponTalent ? 'weapons' : 'gear'; return { id: String(row.id), name: String(row.name), kicker: `${slotLabel} TALENT`, variant: 'talent', iconUrl: iconUrl('talent', row.id, row.icon_updated_at ?? row.updated_at), headerMetaIcon: isWeaponTalent ? 'smg' : gearIcon(normalizedCategory), headerBadge: isWeaponTalent && weaponSlot ? `${weaponSlot} · ${weaponSlot === 1 ? 'TOP' : 'BOTTOM'}` : undefined, badges: [ ...(row.weapon_scope === 'Exotic' ? [{ label: 'EXOTIC', tone: 'exotic' }] : []) ], facts: [], 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', afterLines, actions: [{ label: `FIND COMPATIBLE ${isWeaponTalent ? 'WEAPONS' : 'GEAR'}`, href: `/database/${destination}?${filterKey}=${encodeURIComponent(String(row.id))}` }] }; }; export const load: PageServerLoad = async ({ params, url }) => { const category = databaseCategory(params.category); if (!category) error(404, 'Database category not found'); const database = await getDatabase(); const { search, pattern } = getRegistrySearch(url); const searchArgs = search ? [pattern] : []; const searchClause = search ? " AND item.name LIKE ? ESCAPE '\\' COLLATE NOCASE" : ''; let queryArgs: unknown[] = searchArgs; let filters: Array<{ key: string; label: string; kind: 'segmented' | 'searchable'; selected: string[]; options: Array<{ value: string; label: string; description?: string; searchText?: string; iconUrl?: string; gameIcon?: GameIconName }>; }> = []; let countSql = ''; let rowsSql = ''; switch (category.slug) { case 'gear': { const [brandRows, talentRows] = await Promise.all([ (await database.prepare(`SELECT brands.id,brands.name, (SELECT GROUP_CONCAT(DISTINCT attributes.name) FROM brand_set_effects effects JOIN stat_attributes attributes ON attributes.id=effects.stat_attribute_id WHERE effects.brand_set_id=brands.id) AS bonus_attributes, icons.updated_at FROM brand_sets brands LEFT JOIN game_data_icons icons ON icons.entity_type='brand_set' AND icons.entity_id=brands.id WHERE brands.active=1 ORDER BY brands.name COLLATE NOCASE`)).all(), (await database.prepare(`SELECT talents.id,talents.name, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='I') AS tier_i_description, icons.updated_at FROM talents LEFT JOIN game_data_icons icons ON icons.entity_type='talent' AND icons.entity_id=talents.id WHERE talents.active=1 AND talents.category IN ('Backpack','Body Armor','Vest') ORDER BY talents.name COLLATE NOCASE`)).all() ]); const slots = ['Holster', 'Gloves', 'Backpack', 'Knee Pads', 'Body Armor', 'Mask']; const validBrands = new Set(brandRows.map((row) => String(row.id))); const validTalents = new Set(talentRows.map((row) => String(row.id))); const selectedSlots = url.searchParams.getAll('slot').filter((value) => slots.includes(value)); const selectedBrands = url.searchParams.getAll('brand').filter((value) => validBrands.has(value)); const selectedTalents = url.searchParams.getAll('talent').filter((value) => validTalents.has(value)); const conditions = ['item.active=1']; queryArgs = []; if (search) { conditions.push("item.name LIKE ? ESCAPE '\\' COLLATE NOCASE"); queryArgs.push(pattern); } if (selectedSlots.length) { conditions.push(`item.slot IN (${selectedSlots.map(() => '?').join(',')})`); queryArgs.push(...selectedSlots); } if (selectedBrands.length) { conditions.push(`EXISTS (SELECT 1 FROM gear_brand_sets filter_brands WHERE filter_brands.gear_id=item.id AND filter_brands.brand_set_id IN (${selectedBrands.map(() => '?').join(',')}))`); queryArgs.push(...selectedBrands); } if (selectedTalents.length) { conditions.push(`EXISTS (SELECT 1 FROM gear_talents filter_talents WHERE filter_talents.gear_id=item.id AND filter_talents.talent_id IN (${selectedTalents.map(() => '?').join(',')}))`); queryArgs.push(...selectedTalents); } const where = conditions.join(' AND '); countSql = `SELECT COUNT(*) AS total FROM gear item WHERE ${where}`; rowsSql = `SELECT item.*,render.id AS render_id,render.updated_at AS render_updated_at FROM gear item LEFT JOIN managed_images render ON render.source_entity_type='gear_render' AND render.source_entity_id=item.id WHERE ${where} ORDER BY item.slot,item.tier,item.name COLLATE NOCASE LIMIT ? OFFSET ?`; filters = [ { key: 'slot', label: 'GEAR SLOT', kind: 'segmented', selected: selectedSlots, options: slots.map((slot) => ({ value: slot, label: slot, gameIcon: gearIcon(slot) })) }, { key: 'brand', label: 'BRAND SETS', kind: 'searchable', selected: selectedBrands, options: brandRows.map((row) => ({ value: String(row.id), label: String(row.name), searchText: String(row.bonus_attributes ?? ''), iconUrl: iconUrl('brand_set', row.id, row.updated_at) })) }, { key: 'talent', label: 'TALENTS', kind: 'searchable', selected: selectedTalents, options: talentRows.map((row) => ({ value: String(row.id), label: String(row.name), description: String(row.tier_i_description ?? ''), iconUrl: iconUrl('talent', row.id, row.updated_at) })) } ]; break; } case 'weapons': { const talentRows = await (await database.prepare(`SELECT DISTINCT talents.id,talents.name, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='I') AS tier_i_description, icons.updated_at FROM weapon_talent_options options JOIN weapons source_weapons ON source_weapons.id=options.weapon_id AND source_weapons.active=1 JOIN talents ON talents.id=options.talent_id AND talents.active=1 LEFT JOIN game_data_icons icons ON icons.entity_type='talent' AND icons.entity_id=talents.id ORDER BY talents.name COLLATE NOCASE`)).all(); const weaponTypes = ['Shotgun', 'Rifle', 'MMR', 'AR', 'LMG', 'SMG']; const damageTypes = ['Blast', 'Pierce', 'Shredding']; const validTalents = new Set(talentRows.map((row) => String(row.id))); const selectedTypes = url.searchParams.getAll('weaponType').filter((value) => weaponTypes.includes(value)); const selectedDamageTypes = url.searchParams.getAll('damageType').filter((value) => damageTypes.includes(value)); const selectedTalents = url.searchParams.getAll('weaponTalent').filter((value) => validTalents.has(value)); const conditions = ['item.active=1']; queryArgs = []; if (search) { conditions.push("item.name LIKE ? ESCAPE '\\' COLLATE NOCASE"); queryArgs.push(pattern); } if (selectedTypes.length) { conditions.push(`item.category IN (${selectedTypes.map(() => '?').join(',')})`); queryArgs.push(...selectedTypes); } if (selectedDamageTypes.length) { conditions.push(`item.damage_type IN (${selectedDamageTypes.map(() => '?').join(',')})`); queryArgs.push(...selectedDamageTypes); } if (selectedTalents.length) { conditions.push(`EXISTS (SELECT 1 FROM weapon_talent_options filter_talents WHERE filter_talents.weapon_id=item.id AND filter_talents.talent_id IN (${selectedTalents.map(() => '?').join(',')}))`); queryArgs.push(...selectedTalents); } const where = conditions.join(' AND '); countSql = `SELECT COUNT(*) AS total FROM weapons item WHERE ${where}`; rowsSql = `SELECT item.*,render.id AS render_id,render.updated_at AS render_updated_at, native.damage_min,native.damage_max,native.reference_level, (SELECT COUNT(*) FROM weapon_talent_options options WHERE options.weapon_id=item.id AND options.talent_slot=1) AS talent_1_count, (SELECT COUNT(*) FROM weapon_talent_options options WHERE options.weapon_id=item.id AND options.talent_slot=2) AS talent_2_count, (SELECT COUNT(*) FROM weapon_category_attribute_bonuses bonuses WHERE bonuses.weapon_category=item.category) AS bonus_count FROM weapons item LEFT JOIN weapon_tier_ranges native ON native.weapon_id=item.id AND native.tier=item.native_tier LEFT JOIN managed_images render ON render.source_entity_type='weapon_render' AND render.source_entity_id=item.id WHERE ${where} ORDER BY item.is_exotic DESC,item.category,item.name COLLATE NOCASE LIMIT ? OFFSET ?`; filters = [ { key: 'weaponType', label: 'WEAPON TYPE', kind: 'segmented', selected: selectedTypes, options: weaponTypes.map((type) => ({ value: type, label: type, gameIcon: weaponIcon(type) })) }, { key: 'damageType', label: 'DAMAGE TYPE', kind: 'segmented', selected: selectedDamageTypes, options: damageTypes.map((type) => ({ value: type, label: type, gameIcon: weaponIcon(type) })) }, { key: 'weaponTalent', label: 'TALENT POOL', kind: 'searchable', selected: selectedTalents, options: talentRows.map((row) => ({ value: String(row.id), label: String(row.name), description: String(row.tier_i_description ?? ''), iconUrl: iconUrl('talent', row.id, row.updated_at) })) } ]; break; } case 'weapon-mods': countSql = `SELECT COUNT(*) AS total FROM weapon_mods item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*,weapons.name AS exotic_weapon, GROUP_CONCAT(DISTINCT categories.weapon_category) AS categories, (SELECT COUNT(*) FROM weapon_mod_secondary_options secondary WHERE secondary.weapon_mod_id=item.id) AS secondary_count, (SELECT COUNT(*) FROM weapon_mod_effects effects WHERE effects.weapon_mod_id=item.id) AS effect_count FROM weapon_mods item LEFT JOIN weapons ON weapons.id=item.exotic_weapon_id LEFT JOIN weapon_mod_compatible_categories categories ON categories.weapon_mod_id=item.id WHERE item.active=1${searchClause} GROUP BY item.id ORDER BY item.slot_type,item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; case 'gear-mods': countSql = `SELECT COUNT(*) AS total FROM gear_mods item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*, (SELECT COUNT(*) FROM gear_mod_secondary_options secondary WHERE secondary.gear_mod_id=item.id) AS secondary_count, (SELECT MIN(primary_level_1_min) FROM gear_mod_quality_values quality WHERE quality.gear_mod_id=item.id) AS level_1_floor, (SELECT MAX(primary_level_20_max) FROM gear_mod_quality_values quality WHERE quality.gear_mod_id=item.id) AS level_20_ceiling FROM gear_mods item WHERE item.active=1${searchClause} ORDER BY item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; case 'os-protocols': countSql = `SELECT COUNT(*) AS total FROM os_protocols item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*,attributes.name AS primary_stat,attributes.unit AS primary_unit, primary_effect.base_value,talents.name AS talent_name,icons.updated_at AS icon_updated_at FROM os_protocols item LEFT JOIN os_protocol_primary_effects primary_effect ON primary_effect.protocol_id=item.id LEFT JOIN stat_attributes attributes ON attributes.id=primary_effect.stat_attribute_id LEFT JOIN os_protocol_talents talents ON talents.protocol_id=item.id LEFT JOIN game_data_icons icons ON icons.entity_type='os_protocol' AND icons.entity_id=item.id WHERE item.active=1${searchClause} ORDER BY item.quality,item.primary_attribute_type,item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; case 'talents': { const talentSlots = [ { value: 'Backpack', label: 'Backpack', gameIcon: 'backpack' as const }, { value: 'Body Armor', label: 'Body Armor', gameIcon: 'body_armor' as const }, { value: 'Weapon 1', label: 'Weapon 1 · Top', gameIcon: 'smg' as const }, { value: 'Weapon 2', label: 'Weapon 2 · Bottom', gameIcon: 'smg' as const } ]; const selectedTalentSlots = url.searchParams.getAll('talentSlot') .filter((value) => talentSlots.some((slot) => slot.value === value)); const conditions = ['item.active=1']; queryArgs = []; if (search) { conditions.push("item.name LIKE ? ESCAPE '\\' COLLATE NOCASE"); queryArgs.push(pattern); } if (selectedTalentSlots.length) { const slotConditions = selectedTalentSlots.map((slot) => { if (slot === 'Backpack') return "item.category='Backpack'"; if (slot === 'Body Armor') return "item.category IN ('Body Armor','Vest')"; queryArgs.push(slot === 'Weapon 1' ? 1 : 2); return `item.category='Weapon' AND EXISTS ( SELECT 1 FROM weapon_talent_slots filter_slots WHERE filter_slots.talent_id=item.id AND filter_slots.slot=? )`; }); conditions.push(`(${slotConditions.map((condition) => `(${condition})`).join(' OR ')})`); } const where = conditions.join(' AND '); countSql = `SELECT COUNT(*) AS total FROM talents item WHERE ${where}`; rowsSql = `SELECT item.*, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=item.id AND tiers.tier='I') AS tier_i, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=item.id AND tiers.tier='II') AS tier_ii, (SELECT slot FROM weapon_talent_slots slots WHERE slots.talent_id=item.id) AS weapon_slot, (SELECT scope FROM talent_weapon_scopes scopes WHERE scopes.talent_id=item.id) AS weapon_scope, icons.updated_at AS icon_updated_at FROM talents item LEFT JOIN game_data_icons icons ON icons.entity_type='talent' AND icons.entity_id=item.id WHERE ${where} ORDER BY item.category,item.name COLLATE NOCASE LIMIT ? OFFSET ?`; filters = [{ key: 'talentSlot', label: 'TALENT SLOT', kind: 'segmented', selected: selectedTalentSlots, options: talentSlots }]; break; } case 'brands': countSql = `SELECT COUNT(*) AS total FROM brand_sets item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*,icons.updated_at AS icon_updated_at FROM brand_sets item LEFT JOIN game_data_icons icons ON icons.entity_type='brand_set' AND icons.entity_id=item.id WHERE item.active=1${searchClause} ORDER BY item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; case 'skill-chips': countSql = `SELECT COUNT(*) AS total FROM skill_chip_sets item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*,bonus.min_value,bonus.max_value,attributes.name AS bonus_stat,attributes.unit AS bonus_unit, talents.name AS talent_name,abilities.name AS ability_name,specializations.name AS specialization_name, (SELECT COUNT(*) FROM skill_chip_primary_options options WHERE options.skill_chip_set_id=item.id) AS primary_count, (SELECT COUNT(*) FROM skill_chip_secondary_options options WHERE options.skill_chip_set_id=item.id) AS secondary_count, icons.updated_at AS icon_updated_at FROM skill_chip_sets item LEFT JOIN skill_chip_two_piece_bonuses bonus ON bonus.skill_chip_set_id=item.id LEFT JOIN stat_attributes attributes ON attributes.id=bonus.stat_attribute_id LEFT JOIN skill_chip_talents talents ON talents.skill_chip_set_id=item.id LEFT JOIN specialization_abilities abilities ON abilities.id=item.specialization_ability_id LEFT JOIN specializations ON specializations.id=abilities.specialization_id LEFT JOIN game_data_icons icons ON icons.entity_type='skill_chip' AND icons.entity_id=item.id WHERE item.active=1${searchClause} ORDER BY item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; case 'specializations': countSql = `SELECT COUNT(*) AS total FROM specializations item WHERE item.active=1${searchClause}`; rowsSql = `SELECT item.*, (SELECT COUNT(*) FROM specialization_focuses focus WHERE focus.specialization_id=item.id AND focus.active=1) AS focus_count, (SELECT COUNT(*) FROM specialization_focuses focus WHERE focus.specialization_id=item.id AND focus.builder_ready=1 AND focus.active=1) AS ready_focus_count, (SELECT COUNT(*) FROM specialization_abilities ability WHERE ability.specialization_id=item.id AND ability.active=1) AS ability_count, (SELECT COUNT(*) FROM specialization_tree_nodes node JOIN specialization_focuses node_focus ON node_focus.id=node.focus_id WHERE node_focus.specialization_id=item.id AND node_focus.active=1 AND node.active=1) AS node_count, (SELECT GROUP_CONCAT(name, ', ') FROM ( SELECT focus.name FROM specialization_focuses focus WHERE focus.specialization_id=item.id AND focus.active=1 ORDER BY focus.display_order,focus.name COLLATE NOCASE )) AS focus_names, (SELECT GROUP_CONCAT(name, ', ') FROM ( SELECT ability.name FROM specialization_abilities ability WHERE ability.specialization_id=item.id AND ability.active=1 ORDER BY ability.name COLLATE NOCASE )) AS ability_names, icons.updated_at AS icon_updated_at FROM specializations item LEFT JOIN game_data_icons icons ON icons.entity_type='specialization' AND icons.entity_id=item.id WHERE item.active=1${searchClause} ORDER BY item.name COLLATE NOCASE LIMIT ? OFFSET ?`; break; } const total = Number((await (await database.prepare(countSql)).get(...queryArgs))?.total ?? 0); const pagination = getPagination(url, total); const rows = await (await database.prepare(rowsSql)).all(...queryArgs, pagination.perPage, pagination.offset); const weaponRelations = category.slug === 'weapons' && rows.length ? await (await database.prepare(`SELECT options.weapon_id,options.talent_slot,talents.*, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='I') AS tier_i, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='II') AS tier_ii, icons.updated_at AS icon_updated_at FROM weapon_talent_options options JOIN talents ON talents.id=options.talent_id AND talents.active=1 LEFT JOIN game_data_icons icons ON icons.entity_type='talent' AND icons.entity_id=talents.id WHERE options.weapon_id IN (${rows.map(() => '?').join(',')}) ORDER BY options.talent_slot,talents.name COLLATE NOCASE`)).all(...rows.map((row) => row.id)) : []; const gearRelations = category.slug === 'gear' && rows.length ? await (await database.prepare(`SELECT relation.*,icons.updated_at FROM ( SELECT links.gear_id,'brand' AS kind,brands.id,brands.name, NULL AS category,NULL AS cooldown_seconds,NULL AS duration_seconds,NULL AS max_stacks, NULL AS tier_i,NULL AS tier_ii FROM gear_brand_sets links JOIN brand_sets brands ON brands.id=links.brand_set_id WHERE brands.active=1 UNION ALL SELECT links.gear_id,'talent' AS kind,talents.id,talents.name, talents.category,talents.cooldown_seconds,talents.duration_seconds,talents.max_stacks, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='I') AS tier_i, (SELECT description FROM talent_tiers tiers WHERE tiers.talent_id=talents.id AND tiers.tier='II') AS tier_ii FROM gear_talents links JOIN talents ON talents.id=links.talent_id WHERE talents.active=1 ) relation LEFT JOIN game_data_icons icons ON icons.entity_id=relation.id AND icons.entity_type=CASE relation.kind WHEN 'brand' THEN 'brand_set' ELSE 'talent' END WHERE relation.gear_id IN (${rows.map(() => '?').join(',')}) ORDER BY relation.kind,relation.name COLLATE NOCASE`)).all(...rows.map((row) => row.id)) : []; const gearBrandEffects = category.slug === 'gear' && gearRelations.some((relation) => relation.kind === 'brand') ? await (await database.prepare(`SELECT effects.brand_set_id,effects.pieces_required,effects.value, attributes.name,attributes.unit FROM brand_set_effects effects JOIN stat_attributes attributes ON attributes.id=effects.stat_attribute_id WHERE effects.brand_set_id IN (${gearRelations.filter((relation) => relation.kind === 'brand').map(() => '?').join(',')}) ORDER BY effects.pieces_required`)).all(...gearRelations.filter((relation) => relation.kind === 'brand').map((relation) => relation.id)) : []; const brandEffects = category.slug === 'brands' && rows.length ? await (await database.prepare(`SELECT effects.brand_set_id,effects.pieces_required,effects.value, attributes.name,attributes.unit FROM brand_set_effects effects 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 talentIds = [...new Set( category.slug === 'talents' ? rows.map((row) => String(row.id)) : category.slug === 'weapons' ? weaponRelations.map((relation) => String(relation.id)) : category.slug === 'gear' ? gearRelations.filter((relation) => relation.kind === 'talent').map((relation) => String(relation.id)) : [] )]; const talentMechanics = talentIds.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 (${talentIds.map(() => '?').join(',')}) ORDER BY rules.source_id,clauses.sort_order,outcomes.outcome_key,outcomes.sort_order`)).all(...talentIds) : []; const mechanicsFor = (talentId: unknown) => talentMechanics.filter((mechanic) => String(mechanic.talent_id) === String(talentId)); const items = rows.map((row) => { switch (category.slug) { case 'gear': { const relations = gearRelations.filter((relation) => relation.gear_id === row.id); const level50Health = gearBaseStatRange(row.base_health_min, row.base_health_max, 50); const level50Armor = row.base_armor_min === null ? null : gearBaseStatRange(row.base_armor_min, row.base_armor_max, 50); const relatedItems = (kind: 'brand' | 'talent', type: GameDataIconType) => relations .filter((relation) => relation.kind === kind) .map((relation) => { const relationIcon = iconUrl(type, relation.id, relation.updated_at); const effects = kind === 'brand' ? gearBrandEffects.filter((effect) => effect.brand_set_id === relation.id) : []; return { id: String(relation.id), name: String(relation.name), iconUrl: relationIcon, preview: kind === 'brand' ? { id: String(relation.id), name: String(relation.name), kicker: 'BRAND SET // EQUIPMENT', iconUrl: relationIcon, factLayout: 'stack' as const, actions: [{ label: 'FIND GEAR WITH THIS BRAND', href: `/database/gear?brand=${encodeURIComponent(String(relation.id))}` }], facts: effects.map((effect) => ({ label: `${effect.pieces_required} PIECES`, value: `${effect.name} · +${valueWithUnit(effect.value,effect.unit)}` })) } : talentCardItem({ ...relation, icon_updated_at: relation.updated_at }, mechanicsFor(relation.id)) }; }); const iconGroups = [ { label: 'BRAND OPTIONS', items: relatedItems('brand', 'brand_set'), emptyLabel: 'NO BRANDS ASSIGNED' }, ...(['Backpack', 'Body Armor'].includes(String(row.slot)) ? [{ label: 'AVAILABLE TALENTS', items: relatedItems('talent', 'talent'), emptyLabel: 'NO TALENTS ASSIGNED' }] : []) ]; return { id: String(row.id), name: String(row.name), kicker: `${row.slot} // T${row.tier}`, renderUrl: row.render_id ? managedImageUrl(String(row.render_id), row.render_updated_at) : '', gameIcon: gearIcon(row.slot), headerBadges: [`${Number(row.tier) === 0 ? 1 : 2} MOD SLOT${Number(row.tier) === 0 ? '' : 'S'}`], headerBadge: `T${row.tier}`, factLayout: 'stack' as const, facts: [ { label: 'BASE HEALTH', value: `${level50Health.min.toLocaleString('en-US')}–${level50Health.max.toLocaleString('en-US')}`, emphasis: 'primary' as const, note: 'AT LEVEL 50' }, ...(level50Armor === null ? [] : [{ label: 'BASE ARMOR', value: `${level50Armor.min.toLocaleString('en-US')}–${level50Armor.max.toLocaleString('en-US')}`, emphasis: 'primary' as const, note: 'AT LEVEL 50' }]) ], iconGroups }; } case 'weapons': { const relatedTalents = weaponRelations.filter((relation) => relation.weapon_id === row.id); const talentItems = (slot: number) => relatedTalents .filter((relation) => Number(relation.talent_slot) === slot) .map((relation) => { const talentIcon = iconUrl('talent', relation.id, relation.icon_updated_at); return { id: String(relation.id), name: String(relation.name), iconUrl: talentIcon, preview: talentCardItem(relation, mechanicsFor(relation.id), slot) }; }); const level50Damage = row.damage_min === null ? null : weaponBaseRange(row.damage_min, row.damage_max, 50); return { id: String(row.id), name: String(row.name), kicker: `${row.category} // ${row.damage_type}`, renderUrl: row.render_id ? managedImageUrl(String(row.render_id), row.render_updated_at) : '', gameIcon: weaponIcon(row.category), headerBadge: `T${row.native_tier}`, headerMetaIcon: weaponIcon(row.damage_type), badges: Number(row.is_exotic) ? [{ label: 'EXOTIC', tone: 'exotic' }] : [], factLayout: 'stack' as const, facts: [ { label: 'DAMAGE', value: level50Damage ? `${level50Damage.min.toLocaleString('en-US')}–${level50Damage.max.toLocaleString('en-US')}` : 'NOT CONFIGURED', emphasis: 'primary' as const, gameIcon: weaponIcon(row.damage_type), note: 'AT LEVEL 50' }, { label: 'RATE / MAG', value: `${number(row.rpm)} RPM · ${number(row.magazine)} MAG` }, { label: 'RELOAD', value: valueWithUnit(row.reload_speed,'s') }, { label: 'HANDLING', value: '', meters: [ { label: 'ACCURACY', value: Number(row.accuracy) || 0 }, { label: 'STABILITY', value: Number(row.stability) || 0 } ] } ], lines: [ { label: 'BASE BONUSES', value: String(row.bonus_count) } ], iconGroups: [ { label: 'TALENT 1 POOL', items: talentItems(1), emptyLabel: 'NO TALENTS ASSIGNED' }, { label: 'TALENT 2 POOL', items: talentItems(2), emptyLabel: 'NO TALENTS ASSIGNED' } ] }; } case 'weapon-mods': return { id: String(row.id), name: String(row.name), kicker: `${row.slot_type} // WEAPON MOD`, badges: row.exotic_weapon_id ? [{ label: 'EXOTIC', tone: 'exotic' }] : [{ label: 'STANDARD → HIGH-END' }], facts: [ { label: 'LEVELS', value: '01–20' }, { label: 'SECONDARY POOL', value: row.exotic_weapon_id ? 'FIXED ATTRIBUTES' : `${row.secondary_count} OPTIONS` }, { label: 'MOD EFFECTS', value: row.exotic_weapon_id ? 'FIXED' : String(row.effect_count) } ], lines: [{ label: row.exotic_weapon_id ? 'ASSIGNED WEAPON' : 'WEAPON CATEGORIES', value: row.exotic_weapon_id ? list(row.exotic_weapon) : list(row.categories).toUpperCase() }] }; case 'gear-mods': return { id: String(row.id), name: String(row.name), kicker: 'GEAR MOD // STANDARD QUALITIES', badges: [{ label: 'STANDARD → HIGH-END' }], facts: [ { label: 'LEVELS', value: '01–20' }, { label: 'PRIMARY OPTIONS', value: '5' }, { label: 'SECONDARY POOL', value: `${row.secondary_count} OPTIONS` } ], lines: [{ label: 'OBSERVED PRIMARY ENVELOPE', value: `${number(row.level_1_floor)} → ${number(row.level_20_ceiling)}` }] }; case 'os-protocols': return { id: String(row.id), name: String(row.name), kicker: `${row.primary_attribute_type} // OS PROTOCOL`, iconUrl: iconUrl('os_protocol',row.id,row.icon_updated_at), badges: [{ label: String(row.quality), tone: String(row.quality).toLowerCase().replace('-','') }], facts: [ { label: 'LEVELS', value: '01–50' }, { label: 'PRIMARY EFFECT', value: row.primary_stat ? `${row.primary_stat} · ${valueWithUnit(row.base_value,row.primary_unit)}` : 'NOT CONFIGURED' }, { label: 'TALENT UPGRADES', value: '0–5' } ], lines: [{ label: 'UNIQUE TALENT', value: list(row.talent_name,'NOT CONFIGURED') }] }; case 'talents': return talentCardItem(row, mechanicsFor(row.id)); case 'brands': { const effects = brandEffects.filter((effect) => effect.brand_set_id === row.id); return { id: String(row.id), name: String(row.name), kicker: 'BRAND SET // EQUIPMENT', iconUrl: iconUrl('brand_set',row.id,row.icon_updated_at), factLayout: 'stack' as const, actions: [{ label: 'FIND GEAR WITH THIS BRAND', href: `/database/gear?brand=${encodeURIComponent(String(row.id))}` }], facts: effects.map((effect) => ({ label: `${effect.pieces_required} PIECES`, value: `${effect.name} · +${valueWithUnit(effect.value,effect.unit)}` })) }; } case 'skill-chips': return { id: String(row.id), name: String(row.name), kicker: 'SKILL CHIP SET // 2–3 PIECES', iconUrl: iconUrl('skill_chip',row.id,row.icon_updated_at), badges: [{ label: 'SUPERIOR' }, { label: 'HIGH-END' }], facts: [ { label: 'SPECIALIZATION ABILITY', value: row.ability_name ? `${row.ability_name} · ${row.specialization_name}` : 'UNASSIGNED' }, { label: '2-PIECE BONUS', value: row.bonus_stat ? `${row.bonus_stat} · ${range(row.min_value,row.max_value,row.bonus_unit)}` : 'NOT CONFIGURED' }, { label: 'PRIMARY POOL', value: `${row.primary_count} OPTIONS` }, { label: 'SECONDARY POOL', value: `${row.secondary_count} OPTIONS` } ], lines: [{ label: '3-PIECE TALENT', value: list(row.talent_name,'NOT CONFIGURED') }] }; case 'specializations': return { id: String(row.id), name: String(row.name), kicker: 'SPECIALIZATION // FOCUS PATHS', iconUrl: iconUrl('specialization',row.id,row.icon_updated_at), badges: Number(row.ready_focus_count) ? [{ label: 'BUILDER READY' }] : [], facts: [ { label: 'FOCUS PATHS', value: String(row.focus_count) }, { label: 'ABILITIES', value: String(row.ability_count) }, { label: 'TREE NODES', value: String(row.node_count) } ], lines: [ { label: 'FOCUS PATHS', value: list(row.focus_names) }, { label: 'ABILITIES', value: list(row.ability_names) } ], description: row.description ? String(row.description) : undefined }; } }); return { category, items, pagination, search, filters }; };