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; if (this.scene.vehicles) { for (const v of this.scene.vehicles) { const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, v.gridX, v.gridY); if (d < minDist) { minDist = d; nearest = v; } } } // Check for chests if (this.scene.chests) { for (const chest of this.scene.chests) { const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, chest.gridX, chest.gridY); if (d < minDist && !chest.isOpened) { minDist = d; nearest = chest; } } } 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; } } // Check for Buildings (Signs included) // Currently decorations don't store data easily accessible here without query. // Assuming nearest logic above covers entities. if (nearest) { console.log('E Interacted with:', nearest.type || nearest.lootTable); if (nearest.type === 'scooter') { nearest.interact(this.scene.player); } else if (nearest.lootTable) { nearest.interact(this.scene.player); } else if (nearest.type === 'zombie') { // Check if already tamed? // If aggressive, combat? E is usually benign interaction. nearest.tame(); } else { // Generic Talk / Toggle // INTEGRATE TRANSLATION this.handleTalk(nearest); } } } handleTalk(npc) { if (!this.scene.hybridSkillSystem) { console.log('Talk with:', npc.type); return; } let text = "..."; let color = '#FFFFFF'; if (npc.type === 'zombie') { text = "Brains... Hungry... Leader?"; text = this.scene.hybridSkillSystem.getSpeechTranslation(text); color = '#55FF55'; } else if (npc.type === 'merchant') { text = "Welcome! I have rare goods."; color = '#FFD700'; // Also triggers UI const uiScene = this.scene.scene.get('UIScene'); if (uiScene) uiScene.showTradeMenu(this.scene.inventorySystem); } else if (npc.type === 'elf') { text = "The forest... it burns... you are not safe."; text = this.scene.hybridSkillSystem.getSpeechTranslation(text); // Maybe Elvish/Mutant dialect? color = '#00FFFF'; } else if (npc.passive) { // Animal noises text = npc.type.includes('cow') ? 'Moo.' : 'Cluck.'; } // Show Floating Text Dialogue this.scene.events.emit('show-floating-text', { x: npc.sprite.x, y: npc.sprite.y - 60, text: text, color: color }); // NPC pauses briefly (handled by AI) } 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.3.1 BOAT DEPLOY if (activeTool === 'boat' && !isAttack && this.scene.oceanSystem) { this.scene.oceanSystem.useBoat(); return; } // 3.4 Check for Vehicles (Scooter) if (!isAttack && this.scene.vehicles) { for (const vehicle of this.scene.vehicles) { // If mounted, dismount (interact with self/vehicle at same pos) // If unmounted, check proximity if (Math.abs(vehicle.gridX - gridX) < 1.5 && Math.abs(vehicle.gridY - gridY) < 1.5) { if (vehicle.interact) { vehicle.interact(this.scene.player); return; // Stop other interactions } } } } // 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 { if (npc.isTamed) { // Open Worker Menu if (uiScene && uiScene.showWorkerMenu) { uiScene.showWorkerMenu(npc); } } else { // TAME ATTEMPT console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY); npc.tame(); } return; } } // 3.6 Check for Livestock Interaction (Milking) if (npc.type.includes('cow') && activeTool === 'bucket' && !isAttack) { if (npc.milkReady) { npc.milkReady = false; npc.milkCooldown = 60000; // 60s cooldown const product = npc.type.includes('mutant') ? 'glowing_milk' : 'milk'; if (invSys) { invSys.addItem(product, 1); this.scene.events.emit('show-floating-text', { x: npc.sprite.x, y: npc.sprite.y - 40, text: `+1 ${product}`, color: '#FFFFFF' }); console.log(`🥛 Milked ${npc.type}: ${product}`); // Optional: Replace bucket with empty? No, bucket is tool. // Maybe replace bucket with bucket_milk if it was a single use item, but let's keep it as tool. } } else { this.scene.events.emit('show-floating-text', { x: npc.sprite.x, y: npc.sprite.y - 40, text: 'Not ready...', color: '#AAAAAA' }); } return; } // NPC interaction complete return; } } } // 3.5 Try Opening Chest if (!isAttack && this.scene.terrainSystem) { const decorKey = `${gridX},${gridY}`; const decor = this.scene.terrainSystem.decorationsMap.get(decorKey); if (decor && decor.type === 'chest') { // Open chest and spawn loot! console.log('📦 Opening treasure chest!'); // Random loot const lootTable = ['item_scrap', 'item_chip', 'item_wood', 'item_stone', 'item_bone']; const lootCount = Phaser.Math.Between(2, 4); for (let i = 0; i < lootCount; i++) { const randomLoot = Phaser.Utils.Array.GetRandom(lootTable); if (this.scene.lootSystem) { const offsetX = Phaser.Math.Between(-1, 1); const offsetY = Phaser.Math.Between(-1, 1); this.scene.lootSystem.spawnLoot(gridX + offsetX, gridY + offsetY, randomLoot); } } // Remove chest this.scene.terrainSystem.removeDecoration(gridX, gridY); return; } } // 4. Try Planting Tree (Manual Planting) if (!isAttack && (activeTool === 'tree_sapling' || activeTool === 'item_sapling')) { const tile = this.scene.terrainSystem.getTile(gridX, gridY); // Dovolimo sajenje samo na travo in dirt if (tile && !tile.hasDecoration && !tile.hasCrop && (tile.type.includes('grass') || tile.type.includes('dirt'))) { const saplingSprite = window.SPRITE_TREE_SAPLING || 'tree_sapling'; this.scene.terrainSystem.addDecoration(gridX, gridY, saplingSprite); // Remove 1 sapling from hand if (this.scene.inventorySystem) { this.scene.inventorySystem.removeItem(activeTool, 1); } // Sound // this.scene.soundManager.playPlant(); return; } } // 4. Farming is now handled via Player Space key (handleFarmingAction) // No click farming anymore // 5. Try damage decoration const id = `${gridX},${gridY}`; if (this.scene.terrainSystem && this.scene.terrainSystem.decorationsMap.has(id)) { const decor = this.scene.terrainSystem.decorationsMap.get(id); // Workstation Interaction if (this.scene.workstationSystem && (decor.type.includes('furnace') || decor.type.includes('mint'))) { const heldItem = activeTool ? { itemId: activeTool } : null; const result = this.scene.workstationSystem.interact(gridX, gridY, heldItem); if (result) return; } // Building Interaction (Upgrade House) if (this.scene.buildingSystem && decor.type.startsWith('struct_house')) { const result = this.scene.buildingSystem.tryUpgrade(gridX, gridY); if (result) return; } // handleTreeHit Logic (User Request) // Preverimo tip in ustrezno orodje let damage = 1; // DREVESA (sapling, healthy, dead, blue) if (decor.type.includes('tree') || decor.type.includes('sapling')) { damage = (activeTool === 'axe') ? 5 : 1; // Povečal damage z sekiro } // KAMNI (rock, stone) else if (decor.type.includes('rock') || decor.type.includes('stone')) { damage = (activeTool === 'pickaxe') ? 5 : 1; } else if (decor.type.includes('bush')) damage = 5; // Apply damage decor.hp -= damage; this.showFloatingText(`-${damage}`, gridX, gridY, '#ffaaaa'); // Sound if (this.scene.soundManager) { if (decor.type.includes('tree')) this.scene.soundManager.playChop(); else this.scene.soundManager.playHit(); // Generic hit for rocks } if (decor.hp <= 0) { // XP REWARD (Requested by Logic) if (this.scene.statsSystem) { this.scene.statsSystem.addXP(5); } const prevType = decor.type; // Logic: If Tree (and not sapling), turn into Sapling immediately (Regrowth) if ((prevType.includes('tree') || prevType.includes('final')) && !prevType.includes('sapling')) { decor.type = window.SPRITE_TREE_SAPLING || 'tree_sapling'; decor.hp = 2; // Fragile sapling decor.scale = 1.0; // Update visual immediately const sprite = this.scene.terrainSystem.visibleDecorations.get(id); if (sprite) { sprite.setTexture(decor.type); sprite.setScale(decor.scale); // Shrink effect to simulate falling/replacement this.scene.tweens.add({ targets: sprite, scaleX: { from: 1.2, to: 1.0 }, scaleY: { from: 1.2, to: 1.0 }, duration: 200 }); } // Drop Wood Only if (this.scene.lootSystem) { this.scene.lootSystem.spawnLoot(gridX, gridY, 'item_wood', Math.floor(Math.random() * 3) + 2); } console.log('🌱 Tree replanted automatically.'); } else { const type = this.scene.terrainSystem.removeDecoration(gridX, gridY); // Loot logic handled here via LootSystem if (this.scene.lootSystem) { if (type.includes('rock') || type.includes('stone')) { this.scene.lootSystem.spawnLoot(gridX, gridY, 'item_stone', Math.floor(Math.random() * 3) + 1); } else if (type.includes('bush') || type.includes('sapling')) { this.scene.lootSystem.spawnLoot(gridX, gridY, 'item_seeds', 1); if (type.includes('sapling')) { this.scene.lootSystem.spawnLoot(gridX, gridY, window.SPRITE_TREE_SAPLING || 'tree_sapling', 1); } } else if (type.includes('flowers')) { this.scene.lootSystem.spawnLoot(gridX, gridY, 'item_seeds', 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) } spawnLoot(gridX, gridY, itemType, count = 1) { // Add items directly to inventory instead of spawning physical loot if (this.scene.inventorySystem) { const added = this.scene.inventorySystem.addItem(itemType, count); if (added) { // Visual feedback this.scene.events.emit('show-floating-text', { x: gridX * 48, y: gridY * 48, text: `+${count} ${itemType}`, color: '#00FF00' }); console.log(`📦 Spawned ${count}x ${itemType} at ${gridX},${gridY}`); } } } }