// NPC Entity // NPC z random walk AI in isometrično podporo class NPC { constructor(scene, gridX, gridY, offsetX = 0, offsetY = 0, type = 'zombie') { this.scene = scene; this.gridX = gridX; this.gridY = gridY; this.type = type; // Terrain offset this.offsetX = offsetX; this.offsetY = offsetY; this.iso = new IsometricUtils(48, 24); // Random walk paramters this.moveSpeed = 100; // px/s (počasnejše od igralca) this.gridMoveTime = 300; // ms za premik (počasneje) // Stanje this.state = 'WANDER'; // WANDER, TAMED, FOLLOW this.isMoving = false; this.pauseTime = 0; this.maxPauseTime = 2000; // Pavza med premiki (2s) // Kreira sprite this.createSprite(); // Začetna pozicija this.updatePosition(); // Naključna začetna pavza this.pauseTime = Math.random() * this.maxPauseTime; } tame() { if (this.state === 'TAMED' || this.type !== 'zombie') return; this.state = 'TAMED'; console.log('🧟❤️ Zombie TAMED!'); // Visual Feedback const headX = this.sprite.x; const headY = this.sprite.y - 40; const heart = this.scene.add.text(headX, headY, '❤️', { fontSize: '20px' }); heart.setOrigin(0.5); heart.setDepth(this.sprite.depth + 100); this.scene.tweens.add({ targets: heart, y: headY - 30, alpha: 0, duration: 1500, onComplete: () => heart.destroy() }); // Change color slightly to indicate friend this.sprite.setTint(0xAAFFAA); } toggleState() { if (this.state === 'WANDER') { this.tame(); } else { // Maybe command to stay/follow? this.state = this.state === 'FOLLOW' ? 'STAY' : 'FOLLOW'; console.log(`Command: ${this.state}`); } } createSprite() { let texKey = `npc_${this.type}`; let isAnimated = false; // Check for animated sprites first if (this.type === 'zombie' && this.scene.textures.exists('zombie_walk')) { texKey = 'zombie_walk'; isAnimated = true; } else if (this.type === 'zombie' && this.scene.textures.exists('zombie_sprite')) { texKey = 'zombie_sprite'; } else if (this.type === 'merchant' && this.scene.textures.exists('merchant_sprite')) { texKey = 'merchant_sprite'; } else if (!this.scene.textures.exists(texKey)) { TextureGenerator.createNPCSprite(this.scene, texKey, this.type); } // Kreira sprite const screenPos = this.iso.toScreen(this.gridX, this.gridY); this.sprite = this.scene.add.sprite( screenPos.x + this.offsetX, screenPos.y + this.offsetY, texKey ); this.sprite.setOrigin(0.5, 1); if (isAnimated) { this.sprite.setScale(1.5); } else { this.sprite.setScale(0.3); } // Depth sorting this.updateDepth(); } // ... loop update ... moveToGrid(targetX, targetY) { // Determine facing direction before moving const dx = targetX - this.gridX; const dy = targetY - this.gridY; const movingRight = (dx > 0) || (dy > 0); this.sprite.setFlipX(!movingRight); this.isMoving = true; this.gridX = targetX; this.gridY = targetY; const targetScreen = this.iso.toScreen(targetX, targetY); // Animation if (this.sprite.texture.key === 'zombie_walk') { this.sprite.play('zombie_walk_anim', true); } // Tween za smooth gibanje this.scene.tweens.add({ targets: this.sprite, x: targetScreen.x + this.offsetX, y: targetScreen.y + this.offsetY, duration: this.gridMoveTime, ease: 'Linear', onComplete: () => { this.isMoving = false; // Stop Animation if (this.sprite.texture.key === 'zombie_walk') { this.sprite.stop(); this.sprite.setFrame(0); } } }); // Posodobi depth this.updateDepth(); } update(delta) { this.updateDepth(); // Continuous depth sorting if (this.isMoving) { return; // Že se premika } // Tamed logic if (this.state === 'TAMED' || this.state === 'FOLLOW') { this.followPlayer(); return; } // Random walk - pavza med premiki this.pauseTime += delta; if (this.pauseTime >= this.maxPauseTime) { this.performRandomWalk(); this.pauseTime = 0; } } followPlayer() { if (!this.scene.player) return; const playerPos = this.scene.player.getPosition(); const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y); // Če je precej dlje, se premakni k njemu if (dist > 2) { const dx = Math.sign(playerPos.x - this.gridX); const dy = Math.sign(playerPos.y - this.gridY); // Move 1 tile towards player let targetX = this.gridX + dx; let targetY = this.gridY + dy; // Avoid occupying SAME tile as player if (targetX === playerPos.x && targetY === playerPos.y) { if (Math.random() < 0.5) targetX = this.gridX; else targetY = this.gridY; } this.moveToGrid(targetX, targetY); } } performRandomWalk() { // Naključna smer (NSEW + možnost obstati) const directions = [ { x: -1, y: 0 }, // North-West { x: 1, y: 0 }, // South-East { x: 0, y: -1 }, // South-West { x: 0, y: 1 }, // North-East { x: 0, y: 0 } // Stay (30% možnost) ]; const dir = Phaser.Math.RND.pick(directions); const targetX = this.gridX + dir.x; const targetY = this.gridY + dir.y; // Preveri kolizijo z robovi const terrainSystem = this.scene.terrainSystem; if (terrainSystem && this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) { // Preveri da ni ista pozicija kot igralec if (this.scene.player) { const playerPos = this.scene.player.getPosition(); if (targetX === playerPos.x && targetY === playerPos.y) { return; // Ne premakni se na igralca } } if (dir.x !== 0 || dir.y !== 0) { this.moveToGrid(targetX, targetY); } } } updatePosition() { const screenPos = this.iso.toScreen(this.gridX, this.gridY); this.sprite.setPosition( screenPos.x + this.offsetX, screenPos.y + this.offsetY ); this.updateDepth(); } updateDepth() { // Pixel perfect sorting if (this.sprite) this.sprite.setDepth(this.sprite.y); } getPosition() { return { x: this.gridX, y: this.gridY }; } destroy() { if (this.sprite) { this.sprite.destroy(); } } }