feat: expand ISAC content modeling
Validate and deploy production / validate (push) Canceled after 0s
Validate and deploy production / deploy (push) Canceled after 0s
Validate and deploy staging / validate (push) Successful in 28s
Validate and deploy staging / deploy (push) Successful in 1m3s

This commit is contained in:
jb
2026-08-03 01:23:20 -04:00
parent d9924a95b9
commit 57068b61cd
90 changed files with 9635 additions and 1270 deletions
+94 -34
View File
@@ -40,6 +40,10 @@ import { recordIsacAudit } from '$lib/server/audit';
import { removePublicComment } from '$lib/server/comment-removal';
import { createNotification } from '$lib/server/notifications';
import type { Actions } from './$types';
import {
publicWeaponModSlots,
weaponModSlotDefinitionByName,
} from '$lib/weapons/mod-slots';
const number = (value: unknown) => {
const numeric = Number(value);
@@ -336,8 +340,7 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
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
(SELECT COUNT(*) FROM weapon_talent_options options WHERE options.weapon_id=item.id AND options.talent_slot=2) AS talent_2_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
@@ -386,7 +389,7 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
rowsSql = `SELECT item.*,weapons.name AS exotic_weapon,
render.id AS render_id,render.updated_at AS render_updated_at,
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_secondary_options) 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
@@ -398,7 +401,7 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
case 'gear-mods':
countSql = `SELECT COUNT(*) AS total FROM gear_mods item WHERE item.active=1${searchClause}`;
rowsSql = `SELECT item.*,render.id AS render_id,render.updated_at AS render_updated_at,
(SELECT COUNT(*) FROM gear_mod_secondary_options secondary WHERE secondary.gear_mod_id=item.id) AS secondary_count,
(SELECT COUNT(*) FROM gear_mod_secondary_options) 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
@@ -409,7 +412,7 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
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,
primary_effect.base_value,talents.name AS talent_name,talents.description_template AS talent_description,icons.updated_at AS icon_updated_at,
render.id AS render_id,render.updated_at AS render_updated_at
FROM os_protocols item
LEFT JOIN os_protocol_primary_effects primary_effect ON primary_effect.protocol_id=item.id
@@ -500,8 +503,8 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
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,
(SELECT COUNT(*) FROM skill_chip_primary_options) AS primary_count,
(SELECT COUNT(*) FROM skill_chip_secondary_options) AS secondary_count,
icons.updated_at AS icon_updated_at,render.id AS render_id,render.updated_at AS render_updated_at
FROM skill_chip_sets item
LEFT JOIN skill_chip_two_piece_bonuses bonus ON bonus.skill_chip_set_id=item.id
@@ -549,6 +552,17 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
const rows = await (
await database.prepare(rowsSql)
).all(...queryArgs, pagination.perPage, pagination.offset);
const protocolTalentEffects =
category.slug === 'os-protocols' && rows.length
? await (
await database.prepare(`SELECT talents.protocol_id,effects.base_value,attributes.unit
FROM os_protocol_talents talents
JOIN os_protocol_talent_effects effects ON effects.talent_id=talents.id
JOIN stat_attributes attributes ON attributes.id=effects.stat_attribute_id
WHERE talents.protocol_id IN (${rows.map(() => '?').join(',')})
ORDER BY talents.protocol_id,effects.sort_order`)
).all(...rows.map((row) => row.id))
: [];
const weaponRelations =
category.slug === 'weapons' && rows.length
? await (
@@ -563,6 +577,22 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
ORDER BY options.talent_slot,talents.name COLLATE NOCASE`)
).all(...rows.map((row) => row.id))
: [];
const weaponModSlotRows =
category.slug === 'weapons' && rows.length
? await (
await database.prepare(`SELECT weapon_id,tier,slot_type FROM weapon_mod_slots
WHERE weapon_id IN (${rows.map(() => '?').join(',')})
ORDER BY weapon_id,tier,slot_type`)
).all(...rows.map((row) => row.id))
: [];
const weaponModSlotIconRows =
category.slug === 'weapons'
? await (
await database.prepare(
"SELECT entity_id,updated_at FROM game_data_icons WHERE entity_type='weapon_mod_slot'",
)
).all()
: [];
const gearRelations =
category.slug === 'gear' && rows.length
? await (
@@ -769,6 +799,36 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
const relatedTalents = weaponRelations.filter(
(relation) => relation.weapon_id === row.id,
);
const modSlots = publicWeaponModSlots(
Number(row.native_tier),
[0, 1, 2].map((tier) => ({
tier,
slots: weaponModSlotRows
.filter(
(slot) =>
slot.weapon_id === row.id && Number(slot.tier) === tier,
)
.map((slot) => String(slot.slot_type)),
})),
).map((slot) => {
const definition = weaponModSlotDefinitionByName(slot.slot);
const icon = definition
? weaponModSlotIconRows.find(
(row) => row.entity_id === definition.id,
)
: null;
return {
...slot,
iconUrl:
definition && icon
? gameDataIconUrl(
'weapon_mod_slot',
definition.id,
icon.updated_at,
)
: '',
};
});
const talentItems = (slot: number) =>
relatedTalents
.filter((relation) => Number(relation.talent_slot) === slot)
@@ -801,11 +861,11 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
? managedImageUrl(String(row.render_id), row.render_updated_at)
: '',
gameIcon: weaponIcon(row.category),
headerBadge: `T${row.native_tier}`,
headerBadges: [
`T${row.native_tier}`,
...(Number(row.is_exotic) ? ['EXOTIC'] : []),
],
headerMetaIcon: weaponIcon(row.damage_type),
badges: Number(row.is_exotic)
? [{ label: 'EXOTIC', tone: 'exotic' }]
: [],
factLayout: 'stack' as const,
facts: [
{
@@ -831,7 +891,7 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
],
},
],
lines: [{ label: 'BASE BONUSES', value: String(row.bonus_count) }],
weaponModSlots: modSlots,
iconGroups: [
{
label: 'TALENT 1 POOL',
@@ -914,28 +974,28 @@ export const load: PageServerLoad = async ({ params, url, cookies }) => {
? managedImageUrl(String(row.render_id), row.render_updated_at)
: '',
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'),
},
],
topBorderTone: String(row.quality).toLowerCase().replace('-', ''),
headerBadge: String(row.quality).toUpperCase(),
headerBadgeTone: String(row.quality).toLowerCase().replace('-', ''),
factLayout: 'stack' as const,
facts: [],
protocolPreview: {
primaryStat: String(row.primary_stat ?? 'NOT CONFIGURED'),
primaryUnit: String(row.primary_unit ?? ''),
primaryBaseValue: Number(row.base_value),
talentDescription: row.talent_description
? String(row.talent_description)
: undefined,
talentEffects: protocolTalentEffects
.filter((effect) => effect.protocol_id === row.id)
.map((effect) => ({
baseValue: Number(effect.base_value),
unit: String(effect.unit ?? ''),
})),
},
description: row.talent_description
? String(row.talent_description)
: undefined,
};
case 'talents':
return talentCardItem(row, mechanicsFor(row.id));