phase 11 koncano

This commit is contained in:
2025-12-08 14:01:41 +01:00
parent 07f0752d81
commit f3d476e843
21 changed files with 1503 additions and 200 deletions

View File

@@ -12,6 +12,13 @@ class BlueprintSystem {
this.unlockedRecipes.add('plank');
this.unlockedRecipes.add('chest');
this.unlockedRecipes.add('fence');
this.unlockedRecipes.add('axe');
this.unlockedRecipes.add('pickaxe');
this.unlockedRecipes.add('hoe');
this.unlockedRecipes.add('sword');
this.unlockedRecipes.add('furnace');
this.unlockedRecipes.add('mint');
this.unlockedRecipes.add('grave');
// Blueprint Definitions (Item ID -> Recipe ID)
this.blueprints = {

View File

@@ -7,13 +7,19 @@ class BuildingSystem {
this.buildingsData = {
fence: { name: 'Fence', cost: { wood: 2 }, w: 1, h: 1 },
wall: { name: 'Stone Wall', cost: { stone: 2 }, w: 1, h: 1 },
house: { name: 'House', cost: { wood: 20, stone: 20, gold: 50 }, w: 1, h: 1 } // Visual is bigger but anchor is 1 tile
house: { name: 'House', cost: { wood: 20, stone: 20, gold: 50 }, w: 1, h: 1 }, // Visual is bigger but anchor is 1 tile
barn: { name: 'Barn', cost: { wood: 50, stone: 10 }, w: 1, h: 1 },
silo: { name: 'Silo', cost: { wood: 30, stone: 30 }, w: 1, h: 1 }
};
// Textures init
if (!this.scene.textures.exists('struct_fence')) TextureGenerator.createStructureSprite(this.scene, 'struct_fence', 'fence');
if (!this.scene.textures.exists('struct_wall')) TextureGenerator.createStructureSprite(this.scene, 'struct_wall', 'wall');
if (!this.scene.textures.exists('struct_house')) TextureGenerator.createStructureSprite(this.scene, 'struct_house', 'house');
if (!this.scene.textures.exists('struct_barn')) TextureGenerator.createStructureSprite(this.scene, 'struct_barn', 'barn');
if (this.scene.textures.exists('struct_silo')) TextureGenerator.createStructureSprite(this.scene, 'struct_silo', 'silo');
// House Lv2 Texture
TextureGenerator.createStructureSprite(this.scene, 'struct_house_lv2', 'house_lv2');
}
toggleBuildMode() {
@@ -85,13 +91,12 @@ class BuildingSystem {
}
// 4. Place Building
// Using decorations layer for now, but marking as building
// Need to add texture to TerrainSystem pool?
// Or better: TerrainSystem should handle 'placing structure'
// Using decorations layer for now
const structType = `struct_${this.selectedBuilding}`;
terrain.addDecoration(gridX, gridY, structType);
// Let's modify TerrainSystem to support 'structures' better or just hack decorations
const success = terrain.placeStructure(gridX, gridY, `struct_${this.selectedBuilding}`);
if (success) {
// Assume success if no error (addDecoration checks internally but doesn't return value easily, but we checked space before)
{
this.showFloatingText(`Built ${building.name}!`, gridX, gridY, '#00FF00');
// Build Sound
@@ -108,6 +113,43 @@ class BuildingSystem {
return true;
}
tryUpgrade(gridX, gridY) {
const terrain = this.scene.terrainSystem;
const decKey = `${gridX},${gridY}`;
const decor = terrain.decorationsMap.get(decKey);
if (!decor) return false;
// Check if House Lv1
if (decor.type === 'struct_house') {
const cost = { wood: 100, stone: 50, gold: 100 };
const inv = this.scene.inventorySystem;
// Check Resources
if (!inv.hasItem('wood', cost.wood) || !inv.hasItem('stone', cost.stone) || inv.gold < cost.gold) {
this.showFloatingText('Upgrade Cost: 100 Wood, 50 Stone, 100G', gridX, gridY, '#FF4444');
return true;
}
// Pay
inv.removeItem('wood', cost.wood);
inv.removeItem('stone', cost.stone);
inv.gold -= cost.gold;
inv.updateUI();
// Perform Upgrade
terrain.removeDecoration(gridX, gridY);
terrain.addDecoration(gridX, gridY, 'struct_house_lv2'); // Re-add Lv2
this.showFloatingText('HOUSE CRADED! (Lv. 2)', gridX, gridY, '#00FFFF');
if (this.scene.soundManager) this.scene.soundManager.playSuccess();
return true;
}
return false;
}
showFloatingText(text, gridX, gridY, color) {
const iso = new IsometricUtils(48, 24);
const pos = iso.toScreen(gridX, gridY);

View File

@@ -0,0 +1,76 @@
class CollectionSystem {
constructor(scene) {
this.scene = scene;
this.unlockedItems = new Set();
// Database of Collectables
this.items = {
// Resources
'wood': { name: 'Wood', desc: 'Basic building material.', category: 'Resource' },
'stone': { name: 'Stone', desc: 'Hard rock for walls.', category: 'Resource' },
'ore_gold': { name: 'Gold Ore', desc: 'Shiny ore found underground.', category: 'Resource' },
'gold_bar': { name: 'Gold Bar', desc: 'Refined gold ingot.', category: 'Resource' },
// Crops
'seeds': { name: 'Seeds', desc: 'Mystery seeds.', category: 'Farming' },
'wheat': { name: 'Wheat', desc: 'Staple grain.', category: 'Farming' },
'corn': { name: 'Corn', desc: 'Tall growing crop.', category: 'Farming' },
// Rare
'item_bone': { name: 'Bone', desc: 'Remains of a zombie.', category: 'Rare' },
'item_scrap': { name: 'Scrap Metal', desc: 'Old machinery parts.', category: 'Rare' },
'item_chip': { name: 'Microchip', desc: 'High-tech component.', category: 'Rare' },
'coin_gold': { name: 'Gold Coin', desc: 'Currency of the new world.', category: 'Rare' },
'artefact_old': { name: 'Ancient Pot', desc: 'A relict from the past.', category: 'Archaeology' },
// Nature
'flower_red': { name: 'Red Flower', desc: 'Beautiful bloom.', category: 'Nature' },
'flower_yellow': { name: 'Yellow Flower', desc: 'Bright bloom.', category: 'Nature' },
'flower_blue': { name: 'Blue Flower', desc: 'Rare blue bloom.', category: 'Nature' },
'mushroom_red': { name: 'Red Mushroom', desc: 'Looks poisonous.', category: 'Nature' },
'mushroom_brown': { name: 'Brown Mushroom', desc: 'Edible fungus.', category: 'Nature' }
};
}
unlock(itemId) {
if (!this.items[itemId]) return; // Not a collectable
if (!this.unlockedItems.has(itemId)) {
this.unlockedItems.add(itemId);
console.log(`📖 Collection Unlocked: ${itemId}`);
// Notification
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 80,
text: `New Collection Entry!`,
color: '#FFD700'
});
if (this.scene.soundManager) {
this.scene.soundManager.playSuccess(); // Reuse success sound
}
}
}
has(itemId) {
return this.unlockedItems.has(itemId);
}
getProgress() {
const total = Object.keys(this.items).length;
const unlocked = this.unlockedItems.size;
return { unlocked, total, percent: (unlocked / total) * 100 };
}
// Save/Load Logic
toJSON() {
return Array.from(this.unlockedItems);
}
load(data) {
if (Array.isArray(data)) {
this.unlockedItems = new Set(data);
}
}
}

View File

@@ -22,7 +22,7 @@ class ExpansionSystem {
id: 'forest',
name: 'Dark Forest',
x: 40, y: 0, w: 60, h: 40, // Right of farm
unlocked: false,
unlocked: true,
cost: 100, // 100 Gold Coins
req: 'None',
color: 0x006400
@@ -31,7 +31,7 @@ class ExpansionSystem {
id: 'city',
name: 'Ruined City',
x: 0, y: 40, w: 100, h: 60, // Below farm & forest
unlocked: false,
unlocked: true,
cost: 500,
req: 'Kill Boss',
color: 0x808080

View File

@@ -50,6 +50,14 @@ class FarmingSystem {
if (!tile.hasDecoration && !tile.hasCrop) {
terrain.setTileType(gridX, gridY, 'farmland');
if (this.scene.soundManager) this.scene.soundManager.playDig();
// Archaeology: Chance to find artefact
if (Math.random() < 0.15) {
if (this.scene.interactionSystem) { // Using InteractionSystem spawnLoot wrapper or LootSystem directly?
// InteractionSystem has spawnLoot method that wraps inventory adding and text.
this.scene.interactionSystem.spawnLoot(gridX, gridY, 'artefact_old', 1);
}
}
return true;
}
}

View File

@@ -0,0 +1,110 @@
class HybridSkillSystem {
constructor(scene) {
this.scene = scene;
this.level = 1;
this.xp = 0;
this.maxXp = 100;
// Skills
this.skills = {
'translation': { level: 0, max: 5, name: 'Zombie Language', desc: 'Understand zombie groans.' },
'strength': { level: 0, max: 5, name: 'Mutant Strength', desc: 'Deal more damage.' },
'resilience': { level: 0, max: 5, name: 'Rot Resistance', desc: 'Less damage from poison/decay.' },
'command': { level: 0, max: 3, name: 'Horde Command', desc: 'Control more zombie workers.' }
};
this.points = 0; // Available skill points
}
addXP(amount) {
this.xp += amount;
if (this.xp >= this.maxXp) {
this.levelUp();
}
// UI Notification
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 50,
text: `+${amount} Hybrid XP`,
color: '#00FF00'
});
}
levelUp() {
this.xp -= this.maxXp;
this.level++;
this.maxXp = Math.floor(this.maxXp * 1.5);
this.points++;
console.log(`🧬 Hybrid Level Up! Level: ${this.level}, Points: ${this.points}`);
this.scene.soundManager.playSuccess(); // Reuse success sound
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 80,
text: `LEVEL UP! (${this.level})`,
color: '#FFFF00'
});
}
tryUpgradeSkill(skillId) {
const skill = this.skills[skillId];
if (!skill) return false;
if (this.points > 0 && skill.level < skill.max) {
this.points--;
skill.level++;
console.log(`🧬 Upgraded ${skill.name} to Lv ${skill.level}`);
return true;
}
return false;
}
getSpeechTranslation(text) {
const lvl = this.skills['translation'].level;
if (lvl >= 5) return text; // Perfect translation
// Obfuscate text based on level
// Lv 0: 100% garbled
// Lv 1: 80% garbled
// ...
const garbleChance = 1.0 - (lvl * 0.2);
return text.split(' ').map(word => {
if (Math.random() < garbleChance) {
return this.garbleWord(word);
}
return word;
}).join(' ');
}
garbleWord(word) {
const sounds = ['hgh', 'arr', 'ghh', '...', 'bra', 'in', 'zZz'];
return sounds[Math.floor(Math.random() * sounds.length)];
}
toJSON() {
return {
level: this.level,
xp: this.xp,
maxXp: this.maxXp,
skills: this.skills,
points: this.points
};
}
load(data) {
if (!data) return;
this.level = data.level;
this.xp = data.xp;
this.maxXp = data.maxXp;
this.points = data.points;
// Merge skills to keep structure if definition changed
for (const k in data.skills) {
if (this.skills[k]) {
this.skills[k].level = data.skills[k].level;
}
}
}
}

View File

@@ -58,24 +58,71 @@ class InteractionSystem {
}
}
// 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) {
// It's a chest!
nearest.interact(this.scene.player);
}
else if (nearest.type === 'zombie') {
// Always Tame on E key (Combat is Space/Click)
// Check if already tamed?
// If aggressive, combat? E is usually benign interaction.
nearest.tame();
} else {
nearest.toggleState(); // Merchant/NPC talk
// Generic Talk / Toggle
// INTEGRATE TRANSLATION
this.handleTalk(nearest);
}
}
}
handleTalk(npc) {
if (!this.scene.hybridSkillSystem) {
npc.toggleState();
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.toggleState(); // Stop moving briefly
}
handleInteraction(gridX, gridY, isAttack = false) {
if (!this.scene.player) return;
@@ -154,9 +201,16 @@ class InteractionSystem {
return;
}
else {
// TAME ATTEMPT
console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY);
npc.tame();
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;
}
}
@@ -233,6 +287,12 @@ class InteractionSystem {
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;

View File

@@ -24,6 +24,11 @@ class InventorySystem {
}
addItem(type, count) {
// Unlock in Collection
if (this.scene.collectionSystem) {
this.scene.collectionSystem.unlock(type);
}
// 1. Try to stack
for (let i = 0; i < this.slots.length; i++) {
if (this.slots[i] && this.slots[i].type === type) {

View File

@@ -21,7 +21,7 @@ const TILE_MINE_WALL = 81; // ID za zid rudnika (Solid/Kolizija)
const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi
const ITEM_IRON = 21; // ID za železo
const TREE_DENSITY_THRESHOLD = 0.65; // Višja vrednost = manj gosto (manj gozda)
const TREE_DENSITY_THRESHOLD = 0.45; // Višja vrednost = manj gosto (manj gozda)
const ROCK_DENSITY_THRESHOLD = 0.60; // Prag za skupine skal
// Terrain Generator System
@@ -119,6 +119,12 @@ class TerrainSystem {
this.offsetX = 0;
this.offsetY = 0;
this.generatedChunks = new Set();
this.chunkSize = 10;
// Init tiles array with NULLs
this.tiles = Array.from({ length: this.height }, () => Array(this.width).fill(null));
}
createTileTextures() {
@@ -224,15 +230,50 @@ class TerrainSystem {
generate() {
this.createTileTextures();
for (let y = 0; y < this.height; y++) {
this.tiles[y] = [];
for (let x = 0; x < this.width; x++) {
console.log('🌍 Initializing World (Zone Streaming Mode)...');
// Generate ONLY the starting area (Farm)
// Farm is at 20,20. Let's load 3x3 chunks around it.
const centerCx = Math.floor(FARM_CENTER_X / this.chunkSize);
const centerCy = Math.floor(FARM_CENTER_Y / this.chunkSize);
for (let cy = centerCy - 2; cy <= centerCy + 2; cy++) {
for (let cx = centerCx - 2; cx <= centerCx + 2; cx++) {
this.generateChunk(cx, cy);
}
}
console.log(`✅ World Init Complete. Loaded ${this.generatedChunks.size} chunks.`);
}
generateChunk(cx, cy) {
const key = `${cx},${cy}`;
if (this.generatedChunks.has(key)) return;
// Bounds check
if (cx < 0 || cy < 0 || cx * this.chunkSize >= this.width || cy * this.chunkSize >= this.height) return;
this.generatedChunks.add(key);
// console.log(`🔄 Streaming Chunk: [${cx}, ${cy}]`);
const startX = cx * this.chunkSize;
const startY = cy * this.chunkSize;
const endX = Math.min(startX + this.chunkSize, this.width);
const endY = Math.min(startY + this.chunkSize, this.height);
const validPositions = []; // Local valid positions for this chunk
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
// --- PER TILE GENERATION LOGIC (Moved from old loop) ---
const nx = x * 0.1;
const ny = y * 0.1;
const elevation = this.noise.noise(nx, ny);
let terrainType = this.terrainTypes.GRASS_FULL;
// Edges of WORLD
if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) {
terrainType = this.terrainTypes.GRASS_FULL;
} else {
@@ -244,136 +285,108 @@ class TerrainSystem {
else if (elevation > 0.85) terrainType = this.terrainTypes.STONE;
}
// Farm Override
if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) {
terrainType = this.terrainTypes.DIRT;
}
// CITY AREA - 15x15 območje z OBZIDJEM
// City Override
if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE &&
y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) {
// Preverimo, ali smo na ROBOVIH (Obzidje)
const isEdge = (x === CITY_START_X ||
x === CITY_START_X + CITY_SIZE - 1 ||
y === CITY_START_Y ||
y === CITY_START_Y + CITY_SIZE - 1);
if (isEdge) {
// OBZIDJE - trdno, igralec ne more čez
terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true };
} else {
// NOTRANJOST MESTA - tlakovci (pavement)
terrainType = this.terrainTypes.PAVEMENT;
// Naključne ruševine v mestu
if (Math.random() < 0.15) {
terrainType = this.terrainTypes.RUINS;
}
}
}
// Create Tile Data
this.tiles[y][x] = {
type: terrainType.name,
texture: terrainType.name,
hasDecoration: false,
hasCrop: false,
solid: terrainType.solid || false // Inherits from terrain type
solid: terrainType.solid || false
};
// Place Trees dynamically during generation
// this.placeTree(x, y, terrainType.name);
// Track valid positions for decorations
if (terrainType.name !== 'water' && terrainType.name !== 'sand' && terrainType.name !== 'stone' && !terrainType.solid) {
// Exclude Farm/City from random decor logic
const isFarm = Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2;
// Place Rocks dynamically
// this.placeRock(x, y, terrainType.name);
}
}
let treeCount = 0;
let rockCount = 0;
let flowerCount = 0;
const validPositions = [];
const isFarm = (x, y) => Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
const isCity = (x, y) => x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2;
for (let y = 5; y < this.height - 5; y++) {
for (let x = 5; x < this.width - 5; x++) {
if (isFarm(x, y) || isCity(x, y)) continue;
const tile = this.tiles[y][x];
if (tile.type !== 'water' && tile.type !== 'sand' && tile.type !== 'stone') {
validPositions.push({ x, y });
if (!isFarm && !isCity) {
validPositions.push({ x, y });
}
}
// Direct Placement Calls (Trees/Rocks) - legacy method support
// this.placeTree(x, y, terrainType.name); // Optional: keep this if preferred over random batch
}
}
// DECORATIONS - Enhanced World Details
console.log('🌸 Adding enhanced decorations...');
// --- CHUNK DECORATION PASS ---
// Instead of global counts, we use probability/density per chunk
// 10x10 = 100 tiles.
// Approx density: 0.2 trees per tile = 20 trees per chunk.
// Natural Path Stones (along roads and random areas)
let pathStoneCount = 0;
for (let i = 0; i < 50; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.addDecoration(pos.x, pos.y, 'path_stone');
pathStoneCount++;
// 1. Random Decorations
validPositions.forEach(pos => {
// Trees
if (Math.random() < 0.05) { // 5% chance per valid tile
this.placeTree(pos.x, pos.y, 'grass'); // force check inside
}
}
// Small decorative rocks
let smallRockCount = 0;
for (let i = 0; i < 80; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const rockType = Math.random() > 0.5 ? 'small_rock_1' : 'small_rock_2';
this.addDecoration(pos.x, pos.y, rockType);
smallRockCount++;
// Rocks
if (Math.random() < 0.02) {
this.placeRock(pos.x, pos.y, 'grass');
}
}
// Flower clusters
flowerCount = 0;
for (let i = 0; i < 100; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
// Flowers
if (Math.random() < 0.05) {
const flowers = ['flower_red', 'flower_yellow', 'flower_blue'];
const flowerType = flowers[Math.floor(Math.random() * flowers.length)];
this.addDecoration(pos.x, pos.y, flowerType);
flowerCount++;
}
}
// Mushrooms (spooky atmosphere)
let mushroomCount = 0;
for (let i = 0; i < 60; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const mushroomType = Math.random() > 0.5 ? 'mushroom_red' : 'mushroom_brown';
this.addDecoration(pos.x, pos.y, mushroomType);
mushroomCount++;
// Path Stones
if (Math.random() < 0.02) {
this.addDecoration(pos.x, pos.y, 'path_stone');
}
}
// Fallen Logs (forest debris)
let logCount = 0;
for (let i = 0; i < 25; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.addDecoration(pos.x, pos.y, 'fallen_log');
logCount++;
}
}
// Puddles (will appear during rain)
this.puddlePositions = [];
for (let i = 0; i < 40; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.puddlePositions.push({ x: pos.x, y: pos.y });
}
}
console.log(`✅ Decorations: ${pathStoneCount} paths, ${smallRockCount} rocks, ${flowerCount} flowers, ${mushroomCount} mushrooms, ${logCount} logs.`);
});
}
updateChunks(camera) {
// Calculate which chunks are in view
const view = camera.worldView;
const buffer = 100; // Load slightly outside view
const p1 = this.iso.toGrid(view.x - buffer, view.y - buffer);
const p2 = this.iso.toGrid(view.x + view.width + buffer, view.y + view.height + buffer);
const minCx = Math.floor(Math.min(p1.x, p2.x) / this.chunkSize);
const maxCx = Math.ceil(Math.max(p1.x, p2.x) / this.chunkSize);
const minCy = Math.floor(Math.min(p1.y, p2.y) / this.chunkSize);
const maxCy = Math.ceil(Math.max(p1.y, p2.y) / this.chunkSize);
for (let cy = minCy; cy <= maxCy; cy++) {
for (let cx = minCx; cx <= maxCx; cx++) {
this.generateChunk(cx, cy);
}
}
}
// Retained helper methods...
damageDecoration(x, y, amount) {
const key = `${x},${y}`;
const decor = this.decorationsMap.get(key);
@@ -704,6 +717,8 @@ class TerrainSystem {
}
updateCulling(camera) {
this.updateChunks(camera);
const view = camera.worldView;
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;

View File

@@ -71,8 +71,26 @@ class WeatherSystem {
if (phase !== this.currentPhase) {
this.currentPhase = phase;
console.log(`🌅 Time of Day: ${phase} (${Math.floor(this.gameTime)}:00)`);
// Trigger Bat Swarm at Dusk
if (phase === 'dusk' && this.scene.worldEventSystem) {
this.scene.worldEventSystem.spawnBatSwarm();
}
}
// Trigger Night Owl at Midnight (00:00)
// We check if seconds crossed 0.
if (Math.abs(this.gameTime - 0.0) < 0.05) {
// Only trigger once per night - check active flag or similar?
// Actually, gameTime will pass 0 very quickly but maybe multiple frames.
// We can use a flag resetting at day.
if (!this.owlTriggered && this.scene.worldEventSystem) {
this.scene.worldEventSystem.spawnNightOwl();
this.owlTriggered = true;
}
}
if (this.gameTime > 5) this.owlTriggered = false; // Reset flag at dawn
// Update UI Clock
const uiScene = this.scene.scene.get('UIScene');
if (uiScene && uiScene.clockText) {

View File

@@ -12,12 +12,14 @@ class WorkstationSystem {
this.recipes = {
'furnace': [
{ input: 'ore_iron', fuel: 'coal', output: 'iron_bar', time: 5000 },
{ input: 'ore_gold', fuel: 'coal', output: 'gold_bar', time: 8000 },
{ input: 'ore_stone', fuel: 'coal', output: 'stone_brick', time: 3000 },
{ input: 'sand', fuel: 'coal', output: 'glass', time: 3000 },
{ input: 'log', fuel: 'log', output: 'charcoal', time: 4000 } // Wood to charcoal
],
'mint': [
{ input: 'iron_bar', fuel: 'coal', output: 'coin_gold', time: 2000, outputCount: 10 }
{ input: 'iron_bar', fuel: 'coal', output: 'coin_gold', time: 2000, outputCount: 10 },
{ input: 'gold_bar', fuel: 'coal', output: 'coin_gold', time: 3000, outputCount: 50 }
],
'campfire': [
{ input: 'raw_meat', fuel: 'stick', output: 'cooked_meat', time: 3000 }

View File

@@ -0,0 +1,137 @@
class WorldEventSystem {
constructor(scene) {
this.scene = scene;
this.activeEvents = [];
this.bats = [];
this.owl = null;
}
/*
* BATS: Visual effect for evening/night
*/
spawnBatSwarm() {
console.log('🦇 Bat Swarm Incoming!');
const count = 10 + Math.floor(Math.random() * 10);
for (let i = 0; i < count; i++) {
// Start right-side of screen, random height
const sx = this.scene.scale.width + 50 + Math.random() * 200;
const sy = Math.random() * (this.scene.scale.height / 2); // Top half
const bat = this.scene.add.sprite(sx, sy, 'bat');
bat.setDepth(2000); // Above most things
bat.setScrollFactor(0); // Screen space (UI layer) or World space?
// Better world space if we want them to feel like part of the world, but screen space is easier for "effect".
// Let's stick to Screen Space for "Ambience".
// Random speed
const speed = 150 + Math.random() * 100;
this.bats.push({ sprite: bat, speed: speed });
}
}
/*
* NIGHT OWL: Delivers a gift
*/
spawnNightOwl() {
if (this.owl) return; // Only one at a time
console.log('🦉 Night Owl Arriving!');
// Spawn top-left
const sx = -50;
const sy = 100;
const owl = this.scene.add.sprite(sx, sy, 'owl');
owl.setDepth(2000);
owl.setScrollFactor(0); // Screen space for delivery
this.owl = {
sprite: owl,
state: 'ARRIVING', // ARRIVING, DELIVERING, LEAVING
timer: 0,
targetX: this.scene.scale.width / 2,
targetY: this.scene.scale.height / 3
};
if (this.scene.soundManager) {
// Play owl hoot sound (placeholder or generic)
// this.scene.soundManager.playHoot();
}
this.scene.events.emit('show-floating-text', {
x: this.scene.player.sprite.x,
y: this.scene.player.sprite.y - 120,
text: '🦉 Hoot Hoot!',
color: '#FFA500'
});
}
update(delta) {
// 1. Bats
for (let i = this.bats.length - 1; i >= 0; i--) {
const b = this.bats[i];
b.sprite.x -= b.speed * (delta / 1000); // Fly left
b.sprite.y += (Math.sin(b.sprite.x * 0.02) * 2); // Wobbly flight
if (b.sprite.x < -50) {
b.sprite.destroy();
this.bats.splice(i, 1);
}
}
// 2. Owl
if (this.owl) {
const o = this.owl;
const speed = 200 * (delta / 1000);
if (o.state === 'ARRIVING') {
const dx = o.targetX - o.sprite.x;
const dy = o.targetY - o.sprite.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) {
o.state = 'DELIVERING';
o.timer = 0;
this.dropGift();
} else {
o.sprite.x += (dx / dist) * speed;
o.sprite.y += (dy / dist) * speed;
}
} else if (o.state === 'DELIVERING') {
o.timer += delta;
if (o.timer > 1000) { // Hover for 1s
o.state = 'LEAVING';
}
} else if (o.state === 'LEAVING') {
o.sprite.x += speed; // Fly right
o.sprite.y -= speed * 0.5; // Fly up
if (o.sprite.x > this.scene.scale.width + 50) {
o.sprite.destroy();
this.owl = null;
}
}
}
}
dropGift() {
console.log('🎁 Owl dropped a gift!');
const p = this.scene.player;
// Spawn loot at player pos (World Space)
if (this.scene.interactionSystem) {
// Random gift: Gold or maybe a rare item
const r = Math.random();
let item = 'flower_red';
let count = 1;
if (r < 0.3) { item = 'coin_gold'; count = 10; }
else if (r < 0.6) { item = 'seeds_corn'; count = 5; }
else if (r < 0.9) { item = 'item_scrap'; count = 3; }
else { item = 'artefact_old'; count = 1; } // Rare!
this.scene.interactionSystem.spawnLoot(p.gridX, p.gridY, item, count);
}
}
}

View File

@@ -63,6 +63,8 @@ class ZombieWorkerSystem {
this.performFarmWork(worker);
} else if (worker.workerData.type === 'MINE') {
this.performMineWork(worker);
} else if (worker.workerData.type === 'CLEAR') {
this.performClearWork(worker);
}
}
}
@@ -176,6 +178,41 @@ class ZombieWorkerSystem {
wd.status = 'IDLE';
}
performClearWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
if (!terrain || !terrain.decorationsMap) return;
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
const t = decor.type;
// Clear trees, bushes, logs, rocks
if (t.startsWith('tree') || t.startsWith('bush') || t === 'fallen_log' || t === 'stone' || t.startsWith('small_rock')) {
terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
// Give some resources
if (this.scene.inventorySystem) {
if (t.startsWith('tree') || t === 'fallen_log' || t.startsWith('bush')) {
this.scene.inventorySystem.addItem('wood', 1);
} else {
this.scene.inventorySystem.addItem('stone', 1);
}
}
console.log(`🧟🪓 Worker CLEARED ${t}`);
wd.status = 'WORKING';
return; // One per tick
}
}
}
}
wd.status = 'IDLE';
}
onWorkerDeath(zombie) {
console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`);