feat(database): refine weapon registry cards

This commit is contained in:
JB
2026-07-26 01:09:22 -04:00
parent 81784f6f02
commit 143a30a23a
7 changed files with 168 additions and 10 deletions
+64 -4
View File
@@ -1,5 +1,6 @@
import { databaseCategory } from '$lib/database/categories';
import type { GameIconName } from '$lib/game-icons';
import { weaponBaseRange } from '$lib/builder/loadout';
import { gameDataIconUrl, type GameDataIconType } from '$lib/server/game-data-icons';
import { getDatabase } from '$lib/server/database';
import { getPagination, getRegistrySearch } from '$lib/server/pagination';
@@ -247,6 +248,17 @@ export const load: PageServerLoad = async ({ params, url }) => {
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 (
@@ -340,21 +352,69 @@ export const load: PageServerLoad = async ({ params, url }) => {
};
}
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: {
id: String(relation.id), name: String(relation.name), kicker: `WEAPON TALENT // SLOT ${slot}`,
iconUrl: talentIcon,
badges: [
...(relation.cooldown_seconds ? [{ label: `${number(relation.cooldown_seconds)}s COOLDOWN` }] : []),
...(relation.duration_seconds ? [{ label: `${number(relation.duration_seconds)}s DURATION` }] : []),
...(relation.max_stacks ? [{ label: `${relation.max_stacks} STACKS` }] : [])
],
facts: [
{ label: 'TIER I', value: relation.tier_i ? 'CONFIGURED' : 'MISSING' },
{ label: 'TIER II', value: relation.tier_ii ? 'CONFIGURED' : 'MISSING' }
],
description: relation.tier_i ? `TIER I · ${relation.tier_i}` : 'Tier I description is not configured.',
secondaryDescription: relation.tier_ii ? `TIER II · ${relation.tier_ii}` : 'Tier II description is not configured.'
}
};
});
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}`,
gameIcon: weaponIcon(row.category),
badges: [{ label: `T${row.native_tier}` }, ...(Number(row.is_exotic) ? [{ label: 'EXOTIC', tone: 'exotic' }] : [])],
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 RANGE', value: row.damage_min === null ? 'NOT CONFIGURED' : `${range(row.damage_min,row.damage_max)} · L${row.reference_level}` },
{
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: 'ACCURACY / STABILITY', value: `${number(row.accuracy)} / ${number(row.stability)}` }
{
label: 'HANDLING',
value: '',
meters: [
{ label: 'ACCURACY', value: Number(row.accuracy) || 0 },
{ label: 'STABILITY', value: Number(row.stability) || 0 }
]
}
],
lines: [
{ label: 'TALENT POOLS', value: `${row.talent_1_count} TOP · ${row.talent_2_count} BOTTOM` },
{ 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`,