/** * Resource Interaction System — Phase 4.3 * * Handles player collecting elements from world resources * (mineral veins, geysers). Press E near a resource to collect. */ import { query } from 'bitecs'; import type { World } from '../ecs/world'; import { Position, Resource, PlayerTag } from '../ecs/components'; import { removeGameEntity } from '../ecs/factory'; import type { Inventory } from './inventory'; /** Metadata for a resource entity (string data that can't go in bitECS arrays) */ export interface ResourceInfo { itemId: string; // element symbol or compound id tileX: number; tileY: number; } /** Result of an interaction attempt */ export interface InteractionResult { type: 'collected' | 'depleted' | 'inventory_full' | 'nothing_nearby'; itemId?: string; remaining?: number; } /** Elements that mineral veins can yield */ export const MINERAL_ELEMENTS = ['Fe', 'Cu', 'Zn', 'Au', 'Sn'] as const; /** Elements that geysers can yield */ export const GEYSER_ELEMENTS = ['S', 'H'] as const; /** * Deterministic element picker based on tile position and seed. * Same (x, y, seed) always gives the same element. */ export function pickResourceElement( tileX: number, tileY: number, seed: number, options: readonly string[], ): string { // Multiplicative hash for spatial distribution const hash = ((tileX * 73856093) ^ (tileY * 19349663) ^ (seed * 83492791)) >>> 0; return options[hash % options.length]; } /** * Interaction system — handles E-key resource collection. * * Finds closest resource in range, adds to inventory, decrements quantity. * Returns null if E not pressed, or InteractionResult describing what happened. */ export function interactionSystem( world: World, justPressedInteract: boolean, inventory: Inventory, resourceData: Map, ): InteractionResult | null { if (!justPressedInteract) return null; // Find player position const players = query(world, [Position, PlayerTag]); if (players.length === 0) return null; const playerEid = players[0]; const px = Position.x[playerEid]; const py = Position.y[playerEid]; // Find closest resource in range let closestEid: number | null = null; let closestDist = Infinity; for (const eid of query(world, [Position, Resource])) { const dx = Position.x[eid] - px; const dy = Position.y[eid] - py; const dist = Math.sqrt(dx * dx + dy * dy); const range = Resource.interactRange[eid]; if (dist <= range && dist < closestDist) { closestEid = eid; closestDist = dist; } } if (closestEid === null) { return { type: 'nothing_nearby' }; } const info = resourceData.get(closestEid); if (!info) return { type: 'nothing_nearby' }; // Try to add to inventory const added = inventory.addItem(info.itemId, 1); if (added === 0) { return { type: 'inventory_full', itemId: info.itemId }; } // Decrement resource quantity Resource.quantity[closestEid] -= 1; if (Resource.quantity[closestEid] <= 0) { // Resource depleted — remove entity removeGameEntity(world, closestEid); resourceData.delete(closestEid); return { type: 'depleted', itemId: info.itemId }; } return { type: 'collected', itemId: info.itemId, remaining: Resource.quantity[closestEid], }; }