Files
shd.build/src/routes/database/[category]/+page.server.ts
T

544 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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';
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
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) : '';
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.* FROM gear item 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.*,
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_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 ${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;
}
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 items = rows.map((row) => {
switch (category.slug) {
case 'gear':
{
const relations = gearRelations.filter((relation) => relation.gear_id === row.id);
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)}`
}))
} : {
id: String(relation.id), name: String(relation.name), kicker: `${relation.category} // GEAR TALENT`,
iconUrl: relationIcon,
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 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}`,
gameIcon: gearIcon(row.slot),
badges: [{ label: `T${row.tier}` }, { label: `${Number(row.tier) === 0 ? 1 : 2} MOD SLOT${Number(row.tier) === 0 ? '' : 'S'}` }],
facts: [
{ label: 'BASE HEALTH', value: range(row.base_health_min,row.base_health_max) },
...(row.base_armor_min === null ? [] : [{ label: 'BASE ARMOR', value: range(row.base_armor_min,row.base_armor_max) }])
],
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: {
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),
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': {
const talentCategory = String(row.category);
const isWeaponTalent = talentCategory === 'Weapon';
const normalizedCategory = talentCategory === 'Vest' ? 'Body Armor' : talentCategory;
const slotLabel = isWeaponTalent && row.weapon_slot ? `WEAPON ${row.weapon_slot}` : normalizedCategory.toUpperCase();
return {
id: String(row.id), name: String(row.name), kicker: `${slotLabel} TALENT`,
iconUrl: iconUrl('talent',row.id,row.icon_updated_at),
headerMetaIcon: isWeaponTalent ? 'smg' : gearIcon(normalizedCategory),
headerBadge: isWeaponTalent && row.weapon_slot
? `${row.weapon_slot} · ${Number(row.weapon_slot) === 1 ? 'TOP' : 'BOTTOM'}`
: undefined,
badges: [
...(row.weapon_scope === 'Exotic' ? [{ label: 'EXOTIC', tone: 'exotic' }] : []),
...(row.duration_seconds ? [{ label: `${number(row.duration_seconds)}s DURATION` }] : []),
...(row.max_stacks ? [{ label: `${row.max_stacks} STACKS` }] : [])
],
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' as const,
afterLines: row.cooldown_seconds ? [{ label: 'TALENT COOLDOWN', value: `${number(row.cooldown_seconds)} SECONDS` }] : []
};
}
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') }]
};
}
});
return { category, items, pagination, search, filters };
};