feat: expand profiles and weapon configuration
Validate and deploy production / validate (push) Successful in 26s
Validate and deploy staging / validate (push) Successful in 25s
Validate and deploy production / deploy (push) Failing after 1m10s
Validate and deploy staging / deploy (push) Failing after 8s

This commit is contained in:
JB
2026-07-28 00:11:40 -04:00
parent 0b3be3587b
commit a434cbe60c
37 changed files with 2209 additions and 800 deletions
+100
View File
@@ -0,0 +1,100 @@
export const WEAPON_MOD_SLOT_TYPES = [
'Optics',
'Muzzle',
'Underbarrel',
'Magazine',
'Barrel',
'Upper Receiver',
'Lower Receiver',
'Trigger'
] as const;
export type WeaponModSlotConfig = {
tier: number;
slots: string[];
};
export function normalizeWeaponModSlotPayload(payload: unknown): WeaponModSlotConfig[] {
if (!Array.isArray(payload)) return [];
const byTier = new Map<number, Set<string>>();
for (const row of payload) {
if (!row || typeof row !== 'object') continue;
const tier = Number('tier' in row ? row.tier : Number.NaN);
if (!Number.isInteger(tier) || tier < 0 || tier > 2) continue;
const slots = 'slots' in row && Array.isArray(row.slots) ? row.slots.map((slot: unknown) => String(slot)) : [];
const collected = byTier.get(tier) ?? new Set<string>();
for (const slot of slots) collected.add(slot);
byTier.set(tier, collected);
}
return [...byTier.entries()]
.sort(([left], [right]) => left - right)
.map(([tier, slots]) => ({ tier, slots: [...slots] }));
}
export function requiredWeaponModSlotCount(tier: number): number {
return tier === 0 ? 4 : 5;
}
export function sharedWeaponModSlots(configurations: WeaponModSlotConfig[]): string[] {
return [...new Set(
configurations
.filter((row) => Number(row.tier) === 1 || Number(row.tier) === 2)
.flatMap((row) => row.slots)
)];
}
export function excludedTierZeroWeaponModSlot(
sharedSlots: string[],
configurations: WeaponModSlotConfig[]
): string {
const tierZeroSlots = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const excluded = sharedSlots.filter((slot) => !tierZeroSlots.includes(slot));
return excluded.length === 1 && tierZeroSlots.every((slot) => sharedSlots.includes(slot)) ? excluded[0] : '';
}
export function expandSharedWeaponModSlots(sharedSlots: string[], tierZeroExcludedSlot: string): WeaponModSlotConfig[] {
const uniqueSharedSlots = [...new Set(sharedSlots)];
return [
{
tier: 0,
slots: tierZeroExcludedSlot
? uniqueSharedSlots.filter((slot) => slot !== tierZeroExcludedSlot)
: []
},
{ tier: 1, slots: [...uniqueSharedSlots] },
{ tier: 2, slots: [...uniqueSharedSlots] }
];
}
export function weaponModSlotsComplete(nativeTier: number, configurations: WeaponModSlotConfig[]): boolean {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
if (configured?.slots.length !== requiredWeaponModSlotCount(tier)) return false;
}
if (nativeTier <= 1) {
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
const tierTwo = configurations.find((row) => Number(row.tier) === 2)?.slots ?? [];
if (tierOne.some((slot) => !tierTwo.includes(slot)) || tierTwo.some((slot) => !tierOne.includes(slot))) return false;
}
if (nativeTier === 0) {
const tierZero = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
if (tierZero.some((slot) => !tierOne.includes(slot))) return false;
}
return true;
}
export function validateWeaponModSlots(nativeTier: number, configurations: WeaponModSlotConfig[]): string | null {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
const slots = configured?.slots ?? [];
const required = requiredWeaponModSlotCount(tier);
if (slots.some((slot) => !WEAPON_MOD_SLOT_TYPES.includes(slot as (typeof WEAPON_MOD_SLOT_TYPES)[number]))) {
return `Tier ${tier} includes an unavailable Weapon Mod slot.`;
}
if (slots.length > required) {
return `Tier ${tier} supports at most ${required} unique Weapon Mod slots.`;
}
}
return null;
}