204 lines
7.4 KiB
JavaScript
204 lines
7.4 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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');
|
|
|
|
// Chop Sound
|
|
if (this.scene.soundManager) {
|
|
this.scene.soundManager.playChop();
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|