ok
This commit is contained in:
476
EMERGENCY_SYSTEMS_RECOVERY/InteractionSystem.js
Normal file
476
EMERGENCY_SYSTEMS_RECOVERY/InteractionSystem.js
Normal file
@@ -0,0 +1,476 @@
|
||||
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') {
|
||||
// ... chest opening logic
|
||||
console.log('📦 Opening treasure chest!');
|
||||
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);
|
||||
}
|
||||
}
|
||||
this.scene.terrainSystem.removeDecoration(gridX, gridY);
|
||||
return;
|
||||
}
|
||||
|
||||
// RUIN INTERACTION
|
||||
if (decor && decor.type.includes('ruin')) {
|
||||
console.log('🏚️ Interacted with Ruin at', gridX, gridY);
|
||||
if (this.scene.townRestorationSystem) {
|
||||
// Find a building that matches this location or type
|
||||
// For now, let's just trigger a generic notification or the first building
|
||||
const building = this.scene.townRestorationSystem.buildings.get('jakob_shop'); // Demo link
|
||||
if (building) {
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: gridX * 48, y: gridY * 48 - 40,
|
||||
text: `Restore ${building.name}?`,
|
||||
color: '#FFD700'
|
||||
});
|
||||
|
||||
// Try start restoration if resources exist
|
||||
if (this.scene.townRestorationSystem.hasMaterials(building.materials)) {
|
||||
this.scene.townRestorationSystem.startRestoration(building.id);
|
||||
} else {
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.showNotification) {
|
||||
ui.showNotification({
|
||||
title: 'Missing Materials',
|
||||
text: `Need Wood: ${building.materials.wood}, Stone: ${building.materials.stone}`,
|
||||
icon: '📦'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user