import type { GameNpc } from "@/payload-types"; type PayloadType = Awaited>; export const NPC_FIRST_NAMES = [ "Marcus", "Sergei", "Adrian", "Marek", "Viktor", "Tomas", "Lukas", "Nikolai", "Janek", "Dominik", "Emil", "Petr", "Karel", "Milan", "Oskar", "Radek", "Fedor", "Ivan", "Andrei", "Boris", "Yuri", "Dmitri", "Alexei", "Pavel", "Stefan", "Wojtek", "Jacek", "Mateusz", "Piotr", "Tadeusz", "Karol", "Zbigniew", "Henrik", "Erling", "Magnus", "Anders", "Lars", "Mikkel", "Cedric", "Antoine", "Basile", "Thibault", ] as const; export const NPC_LAST_NAMES = [ "Novak", "Kovac", "Havlicek", "Svoboda", "Dvorak", "Cermak", "Prochazka", "Jelinek", "Zeman", "Kral", "Barta", "Fiala", "Urban", "Pospisil", "Kowalski", "Nowak", "Wozniak", "Lewandowski", "Sokolov", "Ivanov", "Petrov", "Smirnov", "Volkov", "Fedorov", "Morozov", "Pavlov", "Kozlov", "Stepanov", "Todorov", "Horvat", "Markovic", "Jovanovic", "Petrovic", "Berg", "Larsen", "Lindqvist", "Virtanen", "Fournier", "Marchand", "Dupont", ] as const; export const NPC_OCCUPATIONS = [ "Weapons Dealer", "Gunsmith", "Arms Dealer", "Munitions Specialist", "Ammo Merchant", "Black Market Trader", "Quartermaster", "Supply Officer", "Field Doctor", "Medic", "Apothecary", "Pharmacist", "Chef", "Brewer", "Blacksmith", "Mechanic", "Tailor", "Carpenter", "Jeweler", "Artisan", "Smuggler", "Fence", "Dealer", "Broker", "Trader", "Collector", "Scavenger", "Forager", "Drifter", "Farmer", "Fisherman", "Miner", "Prospector", "Radio Operator", ] as const; export function randomOf(arr: readonly T[]): T { return arr[Math.floor(Math.random() * arr.length)]; } /** Random "First Last" name for a generated NPC. */ export function randomNpcName(): string { return `${randomOf(NPC_FIRST_NAMES)} ${randomOf(NPC_LAST_NAMES)}`; } /** Random occupation for a generated NPC, biased toward market roles. */ export function randomNpcOccupation(): string { return randomOf(NPC_OCCUPATIONS); } /** * Create a randomly-generated NPC placeholder. `isGenerated` is true and * `isInGame` is false so the GM can later flip them on to include them in * the game. If `assetId` is given, the NPC is set up as a vendor selling it. */ export async function createGeneratedNpc( payload: PayloadType, opts: { assetId?: number } = {}, ): Promise { return (await payload.create({ collection: "game-npcs", data: { name: randomNpcName(), occupation: randomNpcOccupation(), isGenerated: true, isInGame: false, vendor: { enabled: true, sells: opts.assetId ? [opts.assetId] : [], }, }, overrideAccess: true, depth: 0, })) as unknown as GameNpc; } /** All enabled vendor NPCs that sell the given asset. */ export async function findVendorNpcs( payload: PayloadType, assetId: number, ): Promise { const result = await payload.find({ collection: "game-npcs", where: { and: [ { "vendor.enabled": { equals: true } }, { "vendor.sells": { equals: assetId } }, ], }, limit: 100, depth: 0, overrideAccess: true, }); return result.docs as unknown as GameNpc[]; } /** * Pick an NPC to stock an auto-generated listing for the given asset: * 1. Prefer NPCs that are officially in the game (GM-assigned vendors). * 2. Otherwise reuse any existing vendor NPC selling the asset. * 3. If none exist, generate a new placeholder NPC for the asset. */ export async function resolveVendorNpc( payload: PayloadType, assetId: number, ): Promise { const vendors = await findVendorNpcs(payload, assetId); if (vendors.length > 0) { const inGame = vendors.filter((npc) => npc.isInGame === true); const pool = inGame.length > 0 ? inGame : vendors; return randomOf(pool); } return createGeneratedNpc(payload, { assetId }); } /** Apply an NPC's price modifier to an auto-generated price. */ export function applyNpcPriceModifier(basePrice: number, npc: GameNpc): number { const modifier = npc.vendor?.priceModifier ?? 1; return Math.max(1, Math.round(basePrice * modifier)); }