feat(database): add weapon discovery filters
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user