phase 11 koncano
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
76
src/systems/CollectionSystem.js
Normal file
76
src/systems/CollectionSystem.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
110
src/systems/HybridSkillSystem.js
Normal file
110
src/systems/HybridSkillSystem.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
137
src/systems/WorldEventSystem.js
Normal file
137
src/systems/WorldEventSystem.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user