Files
novafarma/src/systems/InteractionSystem.js
2025-12-08 01:39:39 +01:00

293 lines
12 KiB
JavaScript

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;
}
}
}
// 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. 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);
// 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)
}
}