275 lines
9.0 KiB
JavaScript
275 lines
9.0 KiB
JavaScript
/**
|
|
* 👹 BIOME ENEMY SYSTEM
|
|
* Spawns biome-specific enemies across the world
|
|
* - Different enemies per biome
|
|
* - Difficulty scaling
|
|
* - Loot drops
|
|
* - Combat integration
|
|
*/
|
|
|
|
class BiomeEnemySystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// All enemies in the world
|
|
this.enemies = [];
|
|
|
|
// Enemy types per biome
|
|
this.enemyTypes = {
|
|
'Grassland': [
|
|
{ type: 'wolf', hp: 30, damage: 5, speed: 1.5, loot: ['meat', 'fur'], color: 0x8B4513 },
|
|
{ type: 'boar', hp: 40, damage: 7, speed: 1.2, loot: ['meat', 'tusk'], color: 0x654321 },
|
|
{ type: 'bandit', hp: 50, damage: 10, speed: 1.0, loot: ['gold', 'sword'], color: 0x696969 }
|
|
],
|
|
'Forest': [
|
|
{ type: 'goblin', hp: 25, damage: 6, speed: 1.3, loot: ['gold', 'dagger'], color: 0x228B22 },
|
|
{ type: 'spider', hp: 20, damage: 4, speed: 1.8, loot: ['web', 'poison'], color: 0x2F4F4F },
|
|
{ type: 'ent', hp: 80, damage: 15, speed: 0.8, loot: ['wood', 'seed'], color: 0x8B4513 }
|
|
],
|
|
'Desert': [
|
|
{ type: 'scorpion', hp: 35, damage: 8, speed: 1.4, loot: ['poison', 'chitin'], color: 0xD2691E },
|
|
{ type: 'mummy', hp: 60, damage: 12, speed: 0.9, loot: ['bandage', 'ancient_coin'], color: 0xDEB887 },
|
|
{ type: 'sand_worm', hp: 100, damage: 20, speed: 0.7, loot: ['scales', 'tooth'], color: 0xC0B090 }
|
|
],
|
|
'Mountain': [
|
|
{ type: 'troll', hp: 90, damage: 18, speed: 0.8, loot: ['stone', 'club'], color: 0x708090 },
|
|
{ type: 'golem', hp: 120, damage: 25, speed: 0.6, loot: ['ore', 'core'], color: 0x696969 },
|
|
{ type: 'harpy', hp: 45, damage: 10, speed: 2.0, loot: ['feather', 'talon'], color: 0x9370DB }
|
|
],
|
|
'Swamp': [
|
|
{ type: 'zombie', hp: 50, damage: 10, speed: 0.9, loot: ['bone', 'rotten_flesh'], color: 0x556B2F },
|
|
{ type: 'will_o_wisp', hp: 30, damage: 8, speed: 2.5, loot: ['essence', 'spark'], color: 0x00FFFF },
|
|
{ type: 'swamp_dragon', hp: 150, damage: 30, speed: 0.7, loot: ['scale', 'heart'], color: 0x2F4F2F }
|
|
]
|
|
};
|
|
|
|
// Spawn density per biome
|
|
this.spawnDensity = {
|
|
'Grassland': 0.02, // 2% per tile
|
|
'Forest': 0.03, // 3%
|
|
'Desert': 0.015, // 1.5%
|
|
'Mountain': 0.025, // 2.5%
|
|
'Swamp': 0.035 // 3.5%
|
|
};
|
|
|
|
console.log('👹 BiomeEnemySystem initialized');
|
|
}
|
|
|
|
// Generate enemy spawns across the world
|
|
generateSpawns(biomeSystem) {
|
|
if (!biomeSystem) return;
|
|
|
|
let enemiesSpawned = 0;
|
|
const sampleRate = 10; // Check every 10th tile
|
|
|
|
for (let x = 0; x < 500; x += sampleRate) {
|
|
for (let y = 0; y < 500; y += sampleRate) {
|
|
const biome = biomeSystem.getBiomeAt(x, y);
|
|
const density = this.spawnDensity[biome] || 0.02;
|
|
|
|
if (Math.random() < density) {
|
|
this.spawnEnemy(x, y, biome);
|
|
enemiesSpawned++;
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Spawned ${enemiesSpawned} enemies across world`);
|
|
}
|
|
|
|
// Spawn single enemy
|
|
spawnEnemy(x, y, biome) {
|
|
const enemyList = this.enemyTypes[biome] || this.enemyTypes['Grassland'];
|
|
const enemyTemplate = enemyList[Math.floor(Math.random() * enemyList.length)];
|
|
|
|
const enemy = {
|
|
x,
|
|
y,
|
|
type: enemyTemplate.type,
|
|
biome,
|
|
hp: enemyTemplate.hp,
|
|
maxHp: enemyTemplate.hp,
|
|
damage: enemyTemplate.damage,
|
|
speed: enemyTemplate.speed,
|
|
loot: [...enemyTemplate.loot],
|
|
color: enemyTemplate.color,
|
|
alive: true,
|
|
sprite: null,
|
|
lastMoveTime: 0
|
|
};
|
|
|
|
this.enemies.push(enemy);
|
|
return enemy;
|
|
}
|
|
|
|
// Create enemy sprite when chunk loads
|
|
createEnemySprite(enemy, chunk) {
|
|
if (enemy.sprite || !enemy.alive) return;
|
|
|
|
const worldX = enemy.x * 48 + 24;
|
|
const worldY = enemy.y * 48 + 24;
|
|
|
|
// Simple circle sprite
|
|
const sprite = this.scene.add.circle(worldX, worldY, 15, enemy.color);
|
|
sprite.setDepth(10 + worldY);
|
|
|
|
// HP bar
|
|
const hpBar = this.scene.add.rectangle(worldX, worldY - 25, 30, 4, 0xFF0000);
|
|
hpBar.setOrigin(0, 0.5);
|
|
hpBar.setDepth(10 + worldY);
|
|
|
|
const hpFill = this.scene.add.rectangle(worldX, worldY - 25, 30, 4, 0x00FF00);
|
|
hpFill.setOrigin(0, 0.5);
|
|
hpFill.setDepth(10 + worldY);
|
|
|
|
enemy.sprite = sprite;
|
|
enemy.hpBar = hpBar;
|
|
enemy.hpFill = hpFill;
|
|
|
|
if (chunk) {
|
|
chunk.sprites.push(sprite);
|
|
chunk.sprites.push(hpBar);
|
|
chunk.sprites.push(hpFill);
|
|
}
|
|
}
|
|
|
|
// Update enemies (AI, movement)
|
|
update(time, delta, playerX, playerY) {
|
|
for (const enemy of this.enemies) {
|
|
if (!enemy.alive || !enemy.sprite) continue;
|
|
|
|
// Simple AI: Move towards player if nearby
|
|
const dist = Math.sqrt((enemy.x - playerX) ** 2 + (enemy.y - playerY) ** 2);
|
|
|
|
if (dist < 10 && time > enemy.lastMoveTime + 500) {
|
|
// Move towards player
|
|
const dx = playerX - enemy.x;
|
|
const dy = playerY - enemy.y;
|
|
const len = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (len > 0) {
|
|
enemy.x += (dx / len) * enemy.speed * 0.1;
|
|
enemy.y += (dy / len) * enemy.speed * 0.1;
|
|
|
|
// Update sprite position
|
|
enemy.sprite.setPosition(enemy.x * 48 + 24, enemy.y * 48 + 24);
|
|
if (enemy.hpBar) {
|
|
enemy.hpBar.setPosition(enemy.x * 48 + 24, enemy.y * 48 - 1);
|
|
enemy.hpFill.setPosition(enemy.x * 48 + 24, enemy.y * 48 - 1);
|
|
}
|
|
}
|
|
|
|
enemy.lastMoveTime = time;
|
|
}
|
|
|
|
// Attack player if very close
|
|
if (dist < 1.5 && time > enemy.lastAttackTime + 2000) {
|
|
this.attackPlayer(enemy);
|
|
enemy.lastAttackTime = time;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enemy attacks player
|
|
attackPlayer(enemy) {
|
|
if (this.scene.player) {
|
|
console.log(`👹 ${enemy.type} attacks for ${enemy.damage} damage!`);
|
|
// TODO: Integrate with player health system
|
|
|
|
// Visual feedback
|
|
if (this.scene.cameras) {
|
|
this.scene.cameras.main.shake(200, 0.005);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player attacks enemy
|
|
damageEnemy(enemy, damage) {
|
|
if (!enemy.alive) return;
|
|
|
|
enemy.hp -= damage;
|
|
|
|
// Update HP bar
|
|
if (enemy.hpFill) {
|
|
const hpPercent = Math.max(0, enemy.hp / enemy.maxHp);
|
|
enemy.hpFill.setScale(hpPercent, 1);
|
|
}
|
|
|
|
console.log(`⚔️ ${enemy.type} takes ${damage} damage! (${enemy.hp}/${enemy.maxHp} HP)`);
|
|
|
|
// Death
|
|
if (enemy.hp <= 0) {
|
|
this.killEnemy(enemy);
|
|
}
|
|
}
|
|
|
|
// Kill enemy and drop loot
|
|
killEnemy(enemy) {
|
|
enemy.alive = false;
|
|
|
|
console.log(`💀 ${enemy.type} died!`);
|
|
|
|
// Drop loot
|
|
if (this.scene.inventorySystem && enemy.loot.length > 0) {
|
|
const lootItem = enemy.loot[Math.floor(Math.random() * enemy.loot.length)];
|
|
const amount = Math.floor(Math.random() * 3) + 1;
|
|
|
|
this.scene.inventorySystem.addItem(lootItem, amount);
|
|
console.log(` 💰 Dropped: ${amount}x ${lootItem}`);
|
|
}
|
|
|
|
// Destroy sprite
|
|
if (enemy.sprite) {
|
|
enemy.sprite.destroy();
|
|
if (enemy.hpBar) enemy.hpBar.destroy();
|
|
if (enemy.hpFill) enemy.hpFill.destroy();
|
|
enemy.sprite = null;
|
|
}
|
|
}
|
|
|
|
// Find nearest enemy to point
|
|
findNearestEnemy(x, y, maxDistance = 2) {
|
|
let nearest = null;
|
|
let minDist = maxDistance;
|
|
|
|
for (const enemy of this.enemies) {
|
|
if (!enemy.alive) continue;
|
|
|
|
const dist = Math.sqrt((enemy.x - x) ** 2 + (enemy.y - y) ** 2);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
nearest = enemy;
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|
|
|
|
// Get statistics
|
|
getStats() {
|
|
const alive = this.enemies.filter(e => e.alive).length;
|
|
const byBiome = {};
|
|
|
|
for (const enemy of this.enemies) {
|
|
if (!enemy.alive) continue;
|
|
byBiome[enemy.biome] = (byBiome[enemy.biome] || 0) + 1;
|
|
}
|
|
|
|
return {
|
|
totalEnemies: this.enemies.length,
|
|
alive,
|
|
dead: this.enemies.length - alive,
|
|
byBiome
|
|
};
|
|
}
|
|
|
|
destroy() {
|
|
this.enemies.forEach(enemy => {
|
|
if (enemy.sprite) enemy.sprite.destroy();
|
|
if (enemy.hpBar) enemy.hpBar.destroy();
|
|
if (enemy.hpFill) enemy.hpFill.destroy();
|
|
});
|
|
this.enemies = [];
|
|
}
|
|
}
|