feat(database): add weapon discovery filters

This commit is contained in:
JB
2026-07-26 00:49:28 -04:00
parent 317e5b39bd
commit 81784f6f02
4 changed files with 84 additions and 10 deletions
+69 -7
View File
@@ -14,7 +14,7 @@ const valueWithUnit = (value: unknown, unit: unknown) => `${number(value)}${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();
const weaponIcon = (category: unknown) => String(category).toLowerCase() as GameIconName;
const iconUrl = (type: GameDataIconType, id: unknown, updatedAt: unknown) =>
updatedAt ? gameDataIconUrl(type, String(id), updatedAt) : '';
@@ -31,7 +31,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
label: string;
kind: 'segmented' | 'searchable';
selected: string[];
options: Array<{ value: string; label: string; iconUrl?: string; gameIcon?: GameIconName }>;
options: Array<{ value: string; label: string; description?: string; searchText?: string; iconUrl?: string; gameIcon?: GameIconName }>;
}> = [];
let countSql = '';
let rowsSql = '';
@@ -39,10 +39,17 @@ export const load: PageServerLoad = async ({ params, url }) => {
switch (category.slug) {
case 'gear': {
const [brandRows, talentRows] = await Promise.all([
(await database.prepare(`SELECT brands.id,brands.name,icons.updated_at FROM brand_sets brands
(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,icons.updated_at FROM talents
(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()
@@ -86,6 +93,7 @@ export const load: PageServerLoad = async ({ params, url }) => {
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)
}))
},
@@ -93,14 +101,49 @@ export const load: PageServerLoad = async ({ params, url }) => {
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':
countSql = `SELECT COUNT(*) AS total FROM weapons item WHERE item.active=1${searchClause}`;
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.*,
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,
@@ -108,9 +151,28 @@ export const load: PageServerLoad = async ({ params, url }) => {
(SELECT COUNT(*) FROM weapon_base_attribute_bonuses bonuses WHERE bonuses.weapon_id=item.id) AS bonus_count
FROM weapons item
LEFT JOIN weapon_tier_ranges native ON native.weapon_id=item.id AND native.tier=item.native_tier
WHERE item.active=1${searchClause}
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,