class InteractionSystem { constructor(scene) { this.scene = scene; this.iso = new IsometricUtils(48, 24); // Input listener setup this.scene.input.on('pointerdown', (pointer) => { if (pointer.button === 0) { // Left Click const worldX = pointer.worldX - this.scene.terrainOffsetX; const worldY = pointer.worldY - this.scene.terrainOffsetY; const gridPos = this.iso.toGrid(worldX, worldY); this.handleInteraction(gridPos.x, gridPos.y, false); } }); // Key E listener (Easy Interaction/Tame) this.scene.input.keyboard.on('keydown-E', () => { this.handleInteractKey(); }); } handleInteractKey() { if (!this.scene.player || !this.scene.npcs) return; const playerPos = this.scene.player.getPosition(); // Find nearest NPC let nearest = null; let minDist = 2.5; // Interaction range const candidates = this.scene.spatialGrid ? this.scene.spatialGrid.query(playerPos.x, playerPos.y) : this.scene.npcs; for (const npc of candidates) { const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, npc.gridX, npc.gridY); if (d < minDist) { minDist = d; nearest = npc; } } if (nearest) { console.log('E Interacted with:', nearest.type); if (nearest.type === 'zombie') { // Always Tame on E key (Combat is Space/Click) nearest.tame(); } else { nearest.toggleState(); // Merchant/NPC talk } } } handleInteraction(gridX, gridY, isAttack = false) { if (!this.scene.player) return; // 3. Check distance const playerPos = this.scene.player.getPosition(); const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, gridX, gridY); // Allow interaction within radius of 2.5 tiles (1.5 for attack) const maxDist = isAttack ? 1.5 : 2.5; if (dist > maxDist) return; // DETERMINE TOOL let activeTool = isAttack ? 'sword' : null; const uiScene = this.scene.scene.get('UIScene'); const invSys = this.scene.inventorySystem; if (uiScene && invSys && !isAttack) { const selectedIdx = uiScene.selectedSlot; const slotData = invSys.slots[selectedIdx]; if (slotData) activeTool = slotData.type; } if (isAttack && invSys) { const selectedIdx = uiScene.selectedSlot; const slotData = invSys.slots[selectedIdx]; if (slotData) activeTool = slotData.type; } // 0. Build Mode Override (Only click) if (!isAttack && this.scene.buildingSystem && this.scene.buildingSystem.isBuildMode) { this.scene.buildingSystem.tryBuild(gridX, gridY); return; } // 3.5 Check for NPC Interaction const candidates = this.scene.spatialGrid ? this.scene.spatialGrid.query(gridX, gridY) : this.scene.npcs; if (candidates) { for (const npc of candidates) { // Increased radius to 1.8 to catch moving NPCs easier if (Math.abs(npc.gridX - gridX) < 1.8 && Math.abs(npc.gridY - gridY) < 1.8) { if (npc.type === 'merchant' && !isAttack) { if (uiScene && invSys) uiScene.showTradeMenu(invSys); return; } if (npc.type === 'zombie') { // Logic: Attack vs Tame const isWeapon = activeTool === 'sword' || activeTool === 'axe' || activeTool === 'pickaxe'; if (isAttack || isWeapon) { // COMBAT let damage = 1; if (activeTool === 'sword') damage = 5; if (activeTool === 'axe') damage = 3; if (activeTool === 'pickaxe') damage = 2; if (npc.takeDamage) { npc.takeDamage(damage); } return; } else { // TAME ATTEMPT console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY); npc.tame(); return; } } if (!isAttack) npc.toggleState(); return; } } } // 4. Try Farming Action if (this.scene.farmingSystem && !isAttack) { const didFarm = this.scene.farmingSystem.interact(gridX, gridY, activeTool); if (didFarm) return; } // 5. Try damage decoration const id = `${gridX},${gridY}`; if (this.scene.terrainSystem.decorationsMap.has(id)) { const decor = this.scene.terrainSystem.decorationsMap.get(id); let damage = 1; if (decor.type === 'tree') { damage = (activeTool === 'axe') ? 3 : 1; } else if (decor.type === 'stone') { damage = (activeTool === 'pickaxe') ? 3 : 1; } else if (decor.type === 'bush') damage = 2; // Apply damage decor.hp -= damage; this.showFloatingText(`${-damage}`, gridX, gridY, '#ffaaaa'); if (decor.hp <= 0) { const type = this.scene.terrainSystem.removeDecoration(gridX, gridY); // Loot logic via LootSystem let loot = 'wood'; if (type === 'stone') loot = 'stone'; if (type === 'bush') loot = 'seeds'; // Maybe berries? if (this.scene.lootSystem) { if (type === 'tree') { this.scene.lootSystem.spawnLoot(gridX, gridY, 'wood', 3); } else { this.scene.lootSystem.spawnLoot(gridX, gridY, loot, 1); } } } else { // Shake visual const sprite = this.scene.terrainSystem.visibleDecorations.get(id); if (sprite) { this.scene.tweens.add({ targets: sprite, x: sprite.x + 2, yoyo: true, duration: 50, repeat: 2 }); sprite.setTint(0xffaaaa); this.scene.time.delayedCall(200, () => sprite.clearTint()); } } } } showFloatingText(text, gridX, gridY, color) { const screenPos = this.iso.toScreen(gridX, gridY); const txt = this.scene.add.text( screenPos.x + this.scene.terrainOffsetX, screenPos.y + this.scene.terrainOffsetY - 30, text, { fontSize: '14px', fill: color, stroke: '#000', strokeThickness: 2 } ).setOrigin(0.5); this.scene.tweens.add({ targets: txt, y: txt.y - 40, alpha: 0, duration: 1000, onComplete: () => txt.destroy() }); } update(delta) { // No logic needed here anymore (loot pickup handled by LootSystem) } }