mapa
This commit is contained in:
127
src/entities/Boss.js
Normal file
127
src/entities/Boss.js
Normal file
@@ -0,0 +1,127 @@
|
||||
class Boss extends NPC {
|
||||
constructor(scene, gridX, gridY) {
|
||||
super(scene, gridX, gridY, scene.terrainOffsetX, scene.terrainOffsetY, 'boss');
|
||||
this.maxHp = 500;
|
||||
this.hp = this.maxHp;
|
||||
this.moveSpeed = 70; // Slower than normal zombies
|
||||
|
||||
// Cooldowns
|
||||
this.summonCooldown = 12000;
|
||||
this.smashCooldown = 6000;
|
||||
this.currentSkillTimer = 0;
|
||||
}
|
||||
|
||||
createSprite() {
|
||||
super.createSprite();
|
||||
// Customize appearance
|
||||
this.sprite.setScale(2.0); // Big Boss
|
||||
this.sprite.setTint(0x6600cc); // Dark Purple
|
||||
|
||||
// Add a crown or something? (Text)
|
||||
this.crown = this.scene.add.text(this.sprite.x, this.sprite.y - 80, '👑', { fontSize: '30px' });
|
||||
this.crown.setOrigin(0.5);
|
||||
this.crown.setDepth(this.sprite.depth + 100);
|
||||
}
|
||||
|
||||
update(delta) {
|
||||
super.update(delta);
|
||||
|
||||
// Update Crown position
|
||||
if (this.crown && this.sprite) {
|
||||
this.crown.setPosition(this.sprite.x, this.sprite.y - 80);
|
||||
this.crown.setDepth(this.sprite.depth + 100);
|
||||
}
|
||||
|
||||
if (this.state === 'CHASE' || this.state === 'WANDER') {
|
||||
this.handleBossSkills(delta);
|
||||
}
|
||||
}
|
||||
|
||||
handleBossSkills(delta) {
|
||||
if (!this.scene.player) return;
|
||||
|
||||
this.currentSkillTimer += delta;
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, this.scene.player.gridX, this.scene.player.gridY);
|
||||
|
||||
// Smash Attack (AoE close range)
|
||||
if (this.currentSkillTimer > this.smashCooldown && dist < 4) {
|
||||
this.smashAttack();
|
||||
this.currentSkillTimer = 0; // Reset timer partially? No, full reset for simpler logic
|
||||
}
|
||||
// Summon Minions (Long range or cooldown)
|
||||
else if (this.currentSkillTimer > this.summonCooldown) {
|
||||
this.summonMinions();
|
||||
this.currentSkillTimer = 5000; // Give back some time so he doesn't wait full CD for smash
|
||||
}
|
||||
}
|
||||
|
||||
smashAttack() {
|
||||
console.log('💥 BOSS SMASH!');
|
||||
this.scene.cameras.main.shake(300, 0.01);
|
||||
|
||||
// Visual Warning
|
||||
const warning = this.scene.add.circle(this.sprite.x, this.sprite.y, 150, 0xff0000, 0.4);
|
||||
warning.setScale(0);
|
||||
this.scene.tweens.add({
|
||||
targets: warning,
|
||||
scale: 1,
|
||||
alpha: 0,
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
warning.destroy();
|
||||
// Damage Logic
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
const d = Phaser.Math.Distance.Between(this.sprite.x, this.sprite.y, playerPos.x, playerPos.y); // Pixel distance
|
||||
if (d < 150) {
|
||||
this.scene.player.takeDamage(30);
|
||||
// Knockback
|
||||
// todo
|
||||
}
|
||||
|
||||
// Sound
|
||||
if (this.scene.soundManager) this.scene.soundManager.playDeath(); // Placeholder for big boom
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
summonMinions() {
|
||||
console.log('🧟🧟🧟 BOSS SUMMONS MINIONS!');
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const angle = (Math.PI * 2 / 3) * i;
|
||||
const dist = 3;
|
||||
const sx = Math.floor(this.gridX + Math.cos(angle) * dist);
|
||||
const sy = Math.floor(this.gridY + Math.sin(angle) * dist);
|
||||
|
||||
if (this.scene.terrainSystem.getTile(sx, sy)) {
|
||||
// Spawn normal zombie
|
||||
// We access GameScene's spawn method or create NPC directly
|
||||
const zombie = new NPC(this.scene, sx, sy, this.scene.terrainOffsetX, this.scene.terrainOffsetY, 'zombie');
|
||||
zombie.state = 'CHASE';
|
||||
this.scene.npcs.push(zombie);
|
||||
|
||||
// Spawn Effect
|
||||
if (this.scene.particleEffects) {
|
||||
const iso = new IsometricUtils(48, 24);
|
||||
const pos = iso.toScreen(sx, sy);
|
||||
this.scene.particleEffects.bloodSplash(pos.x + 300, pos.y + 100); // hardcoded offset fix maybe needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.crown) this.crown.destroy();
|
||||
super.destroy();
|
||||
|
||||
// Boss Drops
|
||||
if (this.scene.lootSystem) {
|
||||
this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, 'item_gold', 50);
|
||||
this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, 'item_sword', 1); // Rare drop
|
||||
}
|
||||
|
||||
// Boss Death Event?
|
||||
this.scene.events.emit('boss_killed');
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,8 @@ class NPC {
|
||||
|
||||
handlePassiveAI(delta) {
|
||||
if (this.state === 'TAMED' || this.state === 'FOLLOW') {
|
||||
// Defensive behavior - attack nearby enemy zombies
|
||||
this.defendPlayer();
|
||||
this.followPlayer();
|
||||
return;
|
||||
}
|
||||
@@ -253,6 +255,56 @@ class NPC {
|
||||
}
|
||||
}
|
||||
|
||||
defendPlayer() {
|
||||
if (!this.scene.npcs || this.attackCooldownTimer > 0) return;
|
||||
|
||||
// Find nearest enemy zombie
|
||||
let nearestEnemy = null;
|
||||
let minDist = 6; // Defense radius
|
||||
|
||||
for (const npc of this.scene.npcs) {
|
||||
if (npc === this || npc.state === 'TAMED' || npc.state === 'FOLLOW') continue;
|
||||
if (npc.type !== 'zombie') continue;
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, npc.gridX, npc.gridY);
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
nearestEnemy = npc;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestEnemy) {
|
||||
// Attack if close enough
|
||||
if (minDist <= 1.5) {
|
||||
this.attackEnemy(nearestEnemy);
|
||||
} else {
|
||||
// Move towards enemy
|
||||
this.moveTowards(nearestEnemy.gridX, nearestEnemy.gridY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attackEnemy(target) {
|
||||
if (this.attackCooldownTimer > 0) return;
|
||||
this.attackCooldownTimer = 1500;
|
||||
|
||||
console.log('🧟❤️ Tamed Zombie DEFENDS!');
|
||||
|
||||
// Attack Animation
|
||||
if (this.sprite) {
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 10,
|
||||
yoyo: true, duration: 100, repeat: 1
|
||||
});
|
||||
}
|
||||
|
||||
// Deal Damage
|
||||
if (target && target.takeDamage) {
|
||||
target.takeDamage(15); // Tamed zombies hit harder!
|
||||
}
|
||||
}
|
||||
|
||||
handleAggressiveAI(delta) {
|
||||
if (!this.scene.player) return;
|
||||
|
||||
@@ -289,6 +341,49 @@ class NPC {
|
||||
}
|
||||
|
||||
moveTowards(targetX, targetY) {
|
||||
// Optimization: if very close, use direct movement (collision checks handled by isValidMove)
|
||||
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, targetX, targetY);
|
||||
|
||||
// Use pathfinding for longer distances
|
||||
if (dist > 2 && this.scene.pathfinding) {
|
||||
|
||||
// Check if we need to recalc path (target changed or no path)
|
||||
const targetKey = `${targetX},${targetY}`;
|
||||
if (!this.currentPath || this.pathTargetKey !== targetKey || this.currentPath.length === 0) {
|
||||
|
||||
// Recalc path (Async Worker)
|
||||
if (!this.isWaitingForPath) {
|
||||
this.isWaitingForPath = true;
|
||||
this.scene.pathfinding.findPath(this.gridX, this.gridY, targetX, targetY, (path) => {
|
||||
this.isWaitingForPath = false;
|
||||
this.currentPath = path;
|
||||
this.pathTargetKey = targetKey;
|
||||
|
||||
// Process path once received
|
||||
if (this.currentPath && this.currentPath.length > 0) {
|
||||
// Remove start node if it's current pos
|
||||
if (this.currentPath[0].x === this.gridX && this.currentPath[0].y === this.gridY) {
|
||||
this.currentPath.shift();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return; // Wait for path
|
||||
}
|
||||
}
|
||||
|
||||
// Follow Path
|
||||
if (this.currentPath && this.currentPath.length > 0) {
|
||||
const step = this.currentPath[0];
|
||||
// Move there
|
||||
this.moveToGrid(step.x, step.y);
|
||||
// Remove step
|
||||
this.currentPath.shift();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Direct Movement (Dumb AI)
|
||||
const dx = Math.sign(targetX - this.gridX);
|
||||
const dy = Math.sign(targetY - this.gridY);
|
||||
let nextX = this.gridX + dx;
|
||||
@@ -415,6 +510,16 @@ class NPC {
|
||||
takeDamage(amount) {
|
||||
this.hp -= amount;
|
||||
|
||||
// Hit Sound
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playHit();
|
||||
}
|
||||
|
||||
// Blood Splash Effect
|
||||
if (this.scene.particleEffects) {
|
||||
this.scene.particleEffects.bloodSplash(this.sprite.x, this.sprite.y - 20);
|
||||
}
|
||||
|
||||
// Show Health Bar
|
||||
if (this.healthBar) {
|
||||
this.healthBar.setVisible(true);
|
||||
@@ -449,6 +554,12 @@ class NPC {
|
||||
|
||||
die() {
|
||||
console.log('🧟💀 Zombie DEAD');
|
||||
|
||||
// Death Sound
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playDeath();
|
||||
}
|
||||
|
||||
// Spawn loot - BONE
|
||||
if (this.scene.lootSystem) {
|
||||
this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, 'item_bone');
|
||||
@@ -456,6 +567,12 @@ class NPC {
|
||||
// Fallback
|
||||
this.scene.interactionSystem.spawnLoot(this.gridX, this.gridY, 'item_bone');
|
||||
}
|
||||
|
||||
// Quest Tracking (Kill)
|
||||
if (this.scene.questSystem) {
|
||||
this.scene.questSystem.trackAction(this.type);
|
||||
}
|
||||
|
||||
this.destroy();
|
||||
|
||||
const idx = this.scene.npcs.indexOf(this);
|
||||
@@ -496,6 +613,33 @@ class NPC {
|
||||
this.addTamedEyes();
|
||||
}
|
||||
|
||||
interact() {
|
||||
console.log('🗣️ Inteacting with NPC:', this.type);
|
||||
|
||||
// Quest Check
|
||||
if (this.scene.questSystem) {
|
||||
const availableQuest = this.scene.questSystem.getAvailableQuest(this.type);
|
||||
if (availableQuest) {
|
||||
console.log('Quest Available from NPC!');
|
||||
// Open Dialog UI
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.showQuestDialog) {
|
||||
ui.showQuestDialog(availableQuest, () => {
|
||||
this.scene.questSystem.startQuest(availableQuest.id);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default behavior (Emote)
|
||||
this.showEmote('👋');
|
||||
// Small jump or animation?
|
||||
if (this.sprite) {
|
||||
this.scene.tweens.add({ targets: this.sprite, y: this.sprite.y - 10, yoyo: true, duration: 100 });
|
||||
}
|
||||
}
|
||||
|
||||
addTamedEyes() {
|
||||
if (this.eyesGroup) return;
|
||||
|
||||
|
||||
@@ -70,6 +70,11 @@ class Player {
|
||||
this.sprite.setRotation(Math.PI / 2); // Lie down
|
||||
console.log('💀 PLAYER DIED');
|
||||
|
||||
// Death Sound
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playDeath();
|
||||
}
|
||||
|
||||
// Show Game Over / Reload
|
||||
const txt = this.scene.add.text(this.scene.cameras.main.midPoint.x, this.scene.cameras.main.midPoint.y, 'YOU DIED', {
|
||||
fontSize: '64px', color: '#ff0000', fontStyle: 'bold', stroke: '#000', strokeThickness: 6
|
||||
@@ -132,6 +137,12 @@ class Player {
|
||||
|
||||
attack() {
|
||||
console.log('⚔️ Player Attack!');
|
||||
|
||||
// Attack Sound
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playAttack();
|
||||
}
|
||||
|
||||
if (this.scene.interactionSystem) {
|
||||
const targetX = this.gridX + this.lastDir.x;
|
||||
const targetY = this.gridY + this.lastDir.y;
|
||||
@@ -198,25 +209,40 @@ class Player {
|
||||
let moved = false;
|
||||
let facingRight = !this.sprite.flipX;
|
||||
|
||||
// WASD
|
||||
// Determine inputs
|
||||
let up = this.keys.up.isDown;
|
||||
let down = this.keys.down.isDown;
|
||||
let left = this.keys.left.isDown;
|
||||
let right = this.keys.right.isDown;
|
||||
|
||||
// Check Virtual Joystick inputs (from UIScene)
|
||||
const ui = this.scene.scene.get('UIScene');
|
||||
if (ui && ui.virtualJoystick) {
|
||||
if (ui.virtualJoystick.up) up = true;
|
||||
if (ui.virtualJoystick.down) down = true;
|
||||
if (ui.virtualJoystick.left) left = true;
|
||||
if (ui.virtualJoystick.right) right = true;
|
||||
}
|
||||
|
||||
// Apply
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
|
||||
if (this.keys.up.isDown) {
|
||||
if (up) {
|
||||
dx = -1; dy = 0;
|
||||
moved = true;
|
||||
facingRight = false;
|
||||
} else if (this.keys.down.isDown) {
|
||||
} else if (down) {
|
||||
dx = 1; dy = 0;
|
||||
moved = true;
|
||||
facingRight = true;
|
||||
}
|
||||
|
||||
if (this.keys.left.isDown) {
|
||||
if (left) {
|
||||
dx = 0; dy = 1;
|
||||
moved = true;
|
||||
facingRight = false;
|
||||
} else if (this.keys.right.isDown) {
|
||||
} else if (right) {
|
||||
dx = 0; dy = -1;
|
||||
moved = true;
|
||||
facingRight = true;
|
||||
@@ -275,6 +301,11 @@ class Player {
|
||||
this.gridX = targetX;
|
||||
this.gridY = targetY;
|
||||
|
||||
// Footstep Sound
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playFootstep();
|
||||
}
|
||||
|
||||
const targetScreen = this.iso.toScreen(targetX, targetY);
|
||||
|
||||
if (this.sprite.texture.key === 'player_walk') {
|
||||
|
||||
Reference in New Issue
Block a user