Phase 7: World Structure (New Direction)

This commit is contained in:
2025-12-08 10:56:47 +01:00
parent b79b70dcc1
commit b750f320fc
7 changed files with 491 additions and 10 deletions

122
src/entities/LootChest.js Normal file
View File

@@ -0,0 +1,122 @@
class LootChest {
constructor(scene, gridX, gridY, lootTable = 'basic') {
this.scene = scene;
this.gridX = gridX;
this.gridY = gridY;
this.lootTable = lootTable;
this.isOpened = false;
this.createSprite();
}
createSprite() {
const screenPos = this.scene.iso.toScreen(this.gridX, this.gridY);
const x = screenPos.x + this.scene.terrainOffsetX;
const y = screenPos.y + this.scene.terrainOffsetY;
// Create chest sprite (using existing chest texture)
this.sprite = this.scene.add.sprite(x, y, 'chest');
this.sprite.setOrigin(0.5, 1);
this.sprite.setDepth(this.scene.iso.getDepth(this.gridX, this.gridY, this.scene.iso.LAYER_OBJECTS));
// Golden glow for unopened chests
if (!this.isOpened) {
this.sprite.setTint(0xFFDD00);
// Gentle floating animation
this.scene.tweens.add({
targets: this.sprite,
y: y - 5,
duration: 1500,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
}
getLootTable() {
const tables = {
'basic': [
{ item: 'seeds', count: 10, chance: 1.0 },
{ item: 'seeds_wheat', count: 5, chance: 0.8 },
{ item: 'wood', count: 10, chance: 0.6 },
{ item: 'stone', count: 10, chance: 0.5 }
],
'farm_starter': [
{ item: 'seeds_wheat', count: 15, chance: 1.0 },
{ item: 'seeds_corn', count: 10, chance: 1.0 },
{ item: 'hoe', count: 1, chance: 1.0 },
{ item: 'watering_can', count: 1, chance: 0.8 },
{ item: 'wood', count: 20, chance: 0.9 }
],
'city': [
{ item: 'gold', count: 50, chance: 1.0 },
{ item: 'stone', count: 30, chance: 0.9 },
{ item: 'iron', count: 10, chance: 0.7 },
{ item: 'seeds_corn', count: 5, chance: 0.6 },
{ item: 'axe', count: 1, chance: 0.3 },
{ item: 'pickaxe', count: 1, chance: 0.3 }
],
'elite': [
{ item: 'gold', count: 100, chance: 1.0 },
{ item: 'iron', count: 25, chance: 1.0 },
{ item: 'diamond', count: 3, chance: 0.5 },
{ item: 'seeds_corn', count: 20, chance: 0.8 }
]
};
return tables[this.lootTable] || tables['basic'];
}
open(player) {
if (this.isOpened) return false;
this.isOpened = true;
this.sprite.clearTint();
this.sprite.setTint(0x888888); // Gray for opened
// Stop animation
this.scene.tweens.killTweensOf(this.sprite);
// Spawn loot
const loot = this.getLootTable();
for (const entry of loot) {
if (Math.random() < entry.chance) {
if (entry.item === 'gold') {
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addGold(entry.count);
}
} else {
if (this.scene.interactionSystem) {
this.scene.interactionSystem.spawnLoot(
this.gridX,
this.gridY,
entry.item,
entry.count
);
}
}
}
}
// Effects
this.scene.events.emit('show-floating-text', {
x: this.gridX * 48,
y: this.gridY * 48,
text: '📦 Chest Opened!',
color: '#FFD700'
});
if (this.scene.soundManager) {
this.scene.soundManager.playHarvest();
}
console.log(`📦 Opened ${this.lootTable} chest at ${this.gridX},${this.gridY}`);
return true;
}
interact(player) {
return this.open(player);
}
}

View File

@@ -0,0 +1,104 @@
class ZombieSpawner {
constructor(scene, gridX, gridY, spawnRadius = 5, maxZombies = 3, respawnTime = 30000) {
this.scene = scene;
this.gridX = gridX;
this.gridY = gridY;
this.spawnRadius = spawnRadius;
this.maxZombies = maxZombies;
this.respawnTime = respawnTime;
this.spawnedZombies = [];
this.respawnTimer = 0;
this.isActive = true;
this.createVisual();
}
createVisual() {
const screenPos = this.scene.iso.toScreen(this.gridX, this.gridY);
const x = screenPos.x + this.scene.terrainOffsetX;
const y = screenPos.y + this.scene.terrainOffsetY;
// Spawner sprite (dark portal/grave)
this.sprite = this.scene.add.sprite(x, y, 'gravestone');
this.sprite.setOrigin(0.5, 1);
this.sprite.setDepth(this.scene.iso.getDepth(this.gridX, this.gridY, this.scene.iso.LAYER_OBJECTS));
this.sprite.setTint(0x440044); // Purple tint for spawner
// Pulsing effect
this.scene.tweens.add({
targets: this.sprite,
alpha: 0.6,
duration: 1000,
yoyo: true,
repeat: -1
});
}
spawn() {
if (this.spawnedZombies.length >= this.maxZombies) return;
// Random position around spawner
const offsetX = Phaser.Math.Between(-this.spawnRadius, this.spawnRadius);
const offsetY = Phaser.Math.Between(-this.spawnRadius, this.spawnRadius);
const spawnX = this.gridX + offsetX;
const spawnY = this.gridY + offsetY;
// Create zombie
const zombie = new NPC(
this.scene,
spawnX,
spawnY,
this.scene.terrainOffsetX,
this.scene.terrainOffsetY,
'zombie'
);
zombie.spawner = this; // Reference back to spawner
this.spawnedZombies.push(zombie);
this.scene.npcs.push(zombie);
// Spawn effect
this.scene.events.emit('show-floating-text', {
x: spawnX * 48,
y: spawnY * 48,
text: '💀 Spawn!',
color: '#FF00FF'
});
if (this.scene.soundManager) {
this.scene.soundManager.playHit(); // Re-use hit sound for spawn
}
console.log(`👹 Spawner at ${this.gridX},${this.gridY} spawned zombie`);
}
removeZombie(zombie) {
const index = this.spawnedZombies.indexOf(zombie);
if (index > -1) {
this.spawnedZombies.splice(index, 1);
}
}
update(delta) {
if (!this.isActive) return;
// Clean up dead zombies
this.spawnedZombies = this.spawnedZombies.filter(z =>
this.scene.npcs.includes(z) && z.hp > 0
);
// Respawn check
if (this.spawnedZombies.length < this.maxZombies) {
this.respawnTimer += delta;
if (this.respawnTimer >= this.respawnTime) {
this.respawnTimer = 0;
this.spawn();
}
}
}
destroy() {
if (this.sprite) this.sprite.destroy();
if (this.particles) this.particles.destroy();
}
}