export type Roll = { statId: string; value: number }; export type GearSelection = { itemId: string; level: number; brandId: string; talentId: string; health: number; armor: number; firepower: number; toughness: number; engineering: number; rolls: [Roll, Roll]; augmentSlots: number[] }; export type WeaponSelection = { itemId: string; level: number; tier: number; damage: number; firepower: number; toughness: number; engineering: number; talent1Id: string; talent2Id: string; rolls: [Roll, Roll] }; export type ChipSelection = { setId: string; quality: 'Superior' | 'High-end'; primary: Roll; secondary: [Roll, Roll] }; export type ProtocolSelection = { itemId: string; level: number; talentUpgrades: number }; export type LoadoutDraft = { version: 1; buildName: string; gear: Record; weapons: [WeaponSelection, WeaponSelection]; chips: [ChipSelection, ChipSelection, ChipSelection]; protocol: ProtocolSelection; activeTalents: string[]; }; export const gearSlots = ['Holster', 'Gloves', 'Backpack', 'Knee Pads', 'Body Armor', 'Mask'] as const; export const blankRoll = (): Roll => ({ statId: '', value: 0 }); export const blankGear = (): GearSelection => ({ itemId: '', level: 50, brandId: '', talentId: '', health: 0, armor: 0, firepower: 0, toughness: 0, engineering: 0, rolls: [blankRoll(), blankRoll()], augmentSlots: [] }); export const blankWeapon = (): WeaponSelection => ({ itemId: '', level: 50, tier: 0, damage: 0, firepower: 0, toughness: 0, engineering: 0, talent1Id: '', talent2Id: '', rolls: [blankRoll(), blankRoll()] }); export const blankChip = (): ChipSelection => ({ setId: '', quality: 'Superior', primary: blankRoll(), secondary: [blankRoll(), blankRoll()] }); export const createDraft = (): LoadoutDraft => ({ version: 1, buildName: 'Untitled loadout', gear: Object.fromEntries(gearSlots.map((slot) => [slot, blankGear()])), weapons: [blankWeapon(), blankWeapon()], chips: [blankChip(), blankChip(), blankChip()], protocol: { itemId: '', level: 1, talentUpgrades: 0 }, activeTalents: [] }); export const clamp = (value: unknown, min: unknown, max: unknown) => { const low = Number(min), high = Number(max), numeric = Number(value); if (!Number.isFinite(numeric)) return Number.isFinite(low) ? low : 0; return Math.min(Number.isFinite(high) ? high : numeric, Math.max(Number.isFinite(low) ? low : numeric, numeric)); }; export const scaled = (min: unknown, max: unknown, progress: number, steps: number) => Number(min) + (Number(max) - Number(min)) * Math.min(steps, Math.max(0, progress)) / steps; export type LevelScaledRange = { rawMin: number; rawMax: number; min: number; max: number; provisional: boolean; }; const itemLevel = (value: unknown) => Math.round(clamp(value, 1, 50)); export const gearMultiplier = (level: unknown) => 0.91 + 0.05 * itemLevel(level); export const weaponMultiplier = (level: unknown) => 1.14 + 0.05 * itemLevel(level); export function levelScaledRange( minimum: unknown, maximum: unknown, targetLevel: unknown, referenceLevel: unknown, source: 'gear' | 'weapon' ): LevelScaledRange { const target = itemLevel(targetLevel); const reference = itemLevel(referenceLevel); const multiplier = source === 'gear' ? gearMultiplier : weaponMultiplier; const ratio = multiplier(target) / multiplier(reference); const configuredMin = Number(minimum); const configuredMax = Number(maximum); const rawMin = (Number.isFinite(configuredMin) ? configuredMin : 0) * ratio; const rawMax = (Number.isFinite(configuredMax) ? configuredMax : 0) * ratio; return { rawMin, rawMax, min: Math.floor(rawMin), max: Math.floor(rawMax), provisional: source === 'gear' ? target < 10 || reference < 10 : target < 40 || reference < 40 }; } export function remapRangeValue(value: unknown, from: LevelScaledRange, to: LevelScaledRange) { const span = from.max - from.min; const position = span > 0 ? (clamp(value, from.min, from.max) - from.min) / span : 0; return Math.round(to.min + (to.max - to.min) * position); }