From 81a7895c10d5d13312cb798d82fc4e94d6340feb Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Mon, 8 Dec 2025 14:16:24 +0100 Subject: [PATCH] phase 12 koncxana --- TASKS.md | 20 ++-- index.html | 1 + src/entities/NPC.js | 17 +++ src/scenes/GameScene.js | 2 + src/scenes/UIScene.js | 69 ++++++++++++ src/systems/BuildingSystem.js | 4 +- src/systems/InteractionSystem.js | 32 ++++++ src/systems/LegacySystem.js | 152 +++++++++++++++++++++----- src/systems/OceanSystem.js | 181 +++++++++++++++++++++++++++++++ src/systems/TerrainSystem.js | 7 +- src/utils/TextureGenerator.js | 101 ++++++++++++++++- 11 files changed, 547 insertions(+), 39 deletions(-) create mode 100644 src/systems/OceanSystem.js diff --git a/TASKS.md b/TASKS.md index 4c9f547..8534c04 100644 --- a/TASKS.md +++ b/TASKS.md @@ -216,18 +216,18 @@ Implementacija jedrnih mehanik iz novega koncepta "Krvava Žetev". ## 🧬 Phase 12: Exploration & Legacy (Endgame) - [ ] **Livestock System** - - [ ] Hlev za živali. - - [ ] Loot Tables: Normalno vs. Mutirano (Mleko vs. Svetleče Mleko). + - [x] Hlev za živali. + - [x] Loot Tables: Normalno vs. Mutirano (Mleko vs. Svetleče Mleko). - [ ] **Ocean System** - - [ ] Potapljanje (animacija, kisik bar). - - [ ] Čoln (Vehicle controller). - - [ ] Generacija Otokov (Island Nodes). -- [ ] **Legacy System (Generacije)** - - [ ] Age Counter (Leta/Letni časi). - - [ ] Marriage Logic + Child Spawn. - - [ ] **Inheritance**: Prenos inventarja/farme na novega lika ob smrti. + - [x] Potapljanje (animacija, kisik bar). + - [x] Čoln (Vehicle controller). + - [x] Generacija Otokov (Island Nodes). +- [x] **Legacy System (Generacije)** + - [x] Age Counter (Leta/Letni časi). + - [x] Marriage Logic + Child Spawn. + - [x] **Inheritance**: Prenos inventarja/farme na novega lika ob smrti. - [ ] **Fractions** - - [ ] Reputation System za Mutante (Dobri/Zlobni). + - [x] Reputation System za Mutante (Dobri/Zlobni). ## 🌡️ Phase 13: Elements & Survival (Hardcore) - [ ] **Weather System v2.0** diff --git a/index.html b/index.html index ee036e0..674fd87 100644 --- a/index.html +++ b/index.html @@ -98,6 +98,7 @@ + diff --git a/src/entities/NPC.js b/src/entities/NPC.js index 93a94c2..a3e42b7 100644 --- a/src/entities/NPC.js +++ b/src/entities/NPC.js @@ -51,6 +51,11 @@ class NPC { this.moveSpeed = type.includes('chicken') ? 120 : 60; // Chickens faster than cows this.gridMoveTime = type.includes('chicken') ? 250 : 500; this.passive = true; // NEW FLAG + + if (type.includes('cow')) { + this.milkReady = true; // Starts milkable + this.milkCooldown = 0; + } } else { this.hp = 20; this.maxHp = 20; @@ -244,6 +249,18 @@ class NPC { return; } + // 3.1 Livestock Production Logic + if (this.type.includes('cow') && !this.milkReady) { + this.milkCooldown -= delta; + if (this.milkCooldown <= 0) { + this.milkReady = true; + // Optional: Visual indicator (milk particle?) + if (this.scene.events) this.scene.events.emit('show-floating-text', { + x: this.sprite.x, y: this.sprite.y - 40, text: '!', color: '#FFFFFF' + }); + } + } + // 3. AI Logic if (this.type !== 'merchant' && this.state !== 'TAMED' && this.state !== 'FOLLOW' && !this.passive) { this.handleAggressiveAI(delta); diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index e0c5441..bc6d5df 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -280,6 +280,8 @@ class GameScene extends Phaser.Scene { this.multiplayerSystem = new MultiplayerSystem(this); this.worldEventSystem = new WorldEventSystem(this); this.hybridSkillSystem = new HybridSkillSystem(this); + this.oceanSystem = new OceanSystem(this); + this.legacySystem = new LegacySystem(this); // Initialize Sound Manager console.log('🎵 Initializing Sound Manager...'); diff --git a/src/scenes/UIScene.js b/src/scenes/UIScene.js index d0e9397..734c58a 100644 --- a/src/scenes/UIScene.js +++ b/src/scenes/UIScene.js @@ -24,6 +24,11 @@ class UIScene extends Phaser.Scene { // this.createDebugInfo(); this.createSettingsButton(); + // Listeners for Age + if (this.gameScene) { + this.gameScene.events.on('update-age-ui', (data) => this.updateAge(data.gen, data.age)); + } + // Resize event this.scale.on('resize', this.resize, this); @@ -58,6 +63,10 @@ class UIScene extends Phaser.Scene { this.craftingRecipes = [ { id: 'axe', name: 'Stone Axe', req: { 'wood': 3, 'stone': 3 }, output: 1, type: 'tool', desc: 'Used for chopping trees.' }, { id: 'pickaxe', name: 'Stone Pickaxe', req: { 'wood': 3, 'stone': 3 }, output: 1, type: 'tool', desc: 'Used for mining rocks.' }, + { id: 'bucket', name: 'Iron Bucket', req: { 'iron_bar': 2 }, output: 1, type: 'tool', desc: 'Used for milking cows.' }, + { id: 'stable', name: 'Stable', req: { 'wood': 40, 'stone': 20 }, output: 1, type: 'building', desc: 'Shelter for animals.', buildingId: 'stable' }, + { id: 'animal_feed', name: 'Animal Feed', req: { 'wheat': 2 }, output: 1, type: 'item', desc: 'Food for livestock.' }, + { id: 'boat', name: 'Wood Boat', req: { 'wood': 25 }, output: 1, type: 'vehicle', desc: 'Travel on water.' }, { id: 'hoe', name: 'Stone Hoe', req: { 'wood': 2, 'stone': 2 }, output: 1, type: 'tool', desc: 'Prepares soil for planting.' }, { id: 'sword', name: 'Stone Sword', req: { 'wood': 5, 'stone': 2 }, output: 1, type: 'weapon', desc: 'Deals damage to zombies.' }, { id: 'fence', name: 'Wood Fence', req: { 'wood': 2 }, output: 1, type: 'building', desc: 'Simple barrier.' }, @@ -66,6 +75,16 @@ class UIScene extends Phaser.Scene { { id: 'mint', name: 'Mint', req: { 'stone': 50, 'iron_bar': 5 }, output: 1, type: 'machine', desc: 'Mints coins from bars.' }, { id: 'grave', name: 'Grave', req: { 'stone': 10 }, output: 1, type: 'furniture', desc: 'Resting place for zombies.' } ]; + + // OXYGEN EVENTS + if (this.gameScene) { + this.gameScene.events.on('update-inventory', () => this.updateInventoryUI()); + this.gameScene.events.on('show-floating-text', (data) => this.showFloatingText(data)); + this.gameScene.events.on('update-oxygen', (data) => this.updateOxygen(data)); + this.gameScene.events.on('hide-oxygen', () => this.hideOxygen()); + } + + this.createOxygenBar(); } // ... (rest of class) ... @@ -563,6 +582,7 @@ class UIScene extends Phaser.Scene { bg.fillStyle(0xDAA520, 0.2); // Goldish bg bg.fillRect(x, y, 130, 30); bg.lineStyle(2, 0xFFD700, 0.8); + bg.lineStyle(2, 0xFFD700, 0.8); bg.strokeRect(x, y, 130, 30); this.goldBg = bg; // Save ref @@ -574,6 +594,16 @@ class UIScene extends Phaser.Scene { fontStyle: 'bold' }); this.goldText.setOrigin(0.5, 0.5); + + // GENERATION / AGE UI (Below Gold) + if (this.genText) this.genText.destroy(); + this.genText = this.add.text(x + 65, y + 40, 'Gen: 1 | Age: 18', { + fontSize: '12px', color: '#AAAAAA' + }).setOrigin(0.5); + } + + updateAge(gen, age) { + if (this.genText) this.genText.setText(`Gen: ${gen} | Age: ${age}`); } updateGold(amount) { @@ -1445,4 +1475,43 @@ class UIScene extends Phaser.Scene { }); } } + + // --- OXYGEN BAR --- + createOxygenBar() { + this.oxygenContainer = this.add.container(this.scale.width / 2, this.scale.height - 100); + this.oxygenContainer.setDepth(2000); + + // Bg + const bg = this.add.rectangle(0, 0, 204, 24, 0x000000); + bg.setStrokeStyle(2, 0xffffff); + this.oxygenContainer.add(bg); + + // Bar + this.oxygenBar = this.add.rectangle(-100, 0, 200, 20, 0x00FFFF); + this.oxygenBar.setOrigin(0, 0.5); + this.oxygenContainer.add(this.oxygenBar); + + // Text + const text = this.add.text(0, -25, 'OXYGEN', { + fontSize: '14px', fontStyle: 'bold', fontFamily: 'monospace', color: '#00FFFF' + }).setOrigin(0.5); + this.oxygenContainer.add(text); + + this.oxygenContainer.setVisible(false); + } + + updateOxygen(data) { + if (!this.oxygenContainer) return; + this.oxygenContainer.setVisible(true); + const percent = Math.max(0, data.current / data.max); + this.oxygenBar.width = 200 * percent; + + // Color warning + if (percent < 0.3) this.oxygenBar.fillColor = 0xFF0000; + else this.oxygenBar.fillColor = 0x00FFFF; + } + + hideOxygen() { + if (this.oxygenContainer) this.oxygenContainer.setVisible(false); + } } diff --git a/src/systems/BuildingSystem.js b/src/systems/BuildingSystem.js index 27ab358..58179da 100644 --- a/src/systems/BuildingSystem.js +++ b/src/systems/BuildingSystem.js @@ -9,7 +9,8 @@ class BuildingSystem { 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 barn: { name: 'Barn', cost: { wood: 50, stone: 10 }, w: 1, h: 1 }, - silo: { name: 'Silo', cost: { wood: 30, stone: 30 }, w: 1, h: 1 } + silo: { name: 'Silo', cost: { wood: 30, stone: 30 }, w: 1, h: 1 }, + stable: { name: 'Stable', cost: { wood: 40, stone: 20 }, w: 1, h: 1 } }; // Textures init @@ -18,6 +19,7 @@ class BuildingSystem { 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'); + if (!this.scene.textures.exists('struct_stable')) TextureGenerator.createStructureSprite(this.scene, 'struct_stable', 'stable'); // House Lv2 Texture TextureGenerator.createStructureSprite(this.scene, 'struct_house_lv2', 'house_lv2'); } diff --git a/src/systems/InteractionSystem.js b/src/systems/InteractionSystem.js index 1adbecc..e98764f 100644 --- a/src/systems/InteractionSystem.js +++ b/src/systems/InteractionSystem.js @@ -157,6 +157,12 @@ class InteractionSystem { return; } + // 3.3.1 BOAT DEPLOY + if (activeTool === 'boat' && !isAttack && this.scene.oceanSystem) { + this.scene.oceanSystem.useBoat(); + return; + } + // 3.4 Check for Vehicles (Scooter) if (!isAttack && this.scene.vehicles) { for (const vehicle of this.scene.vehicles) { @@ -215,6 +221,32 @@ class InteractionSystem { } } + // 3.6 Check for Livestock Interaction (Milking) + if (npc.type.includes('cow') && activeTool === 'bucket' && !isAttack) { + if (npc.milkReady) { + npc.milkReady = false; + npc.milkCooldown = 60000; // 60s cooldown + + const product = npc.type.includes('mutant') ? 'glowing_milk' : 'milk'; + + if (invSys) { + invSys.addItem(product, 1); + this.scene.events.emit('show-floating-text', { + x: npc.sprite.x, y: npc.sprite.y - 40, text: `+1 ${product}`, color: '#FFFFFF' + }); + console.log(`🥛 Milked ${npc.type}: ${product}`); + + // Optional: Replace bucket with empty? No, bucket is tool. + // Maybe replace bucket with bucket_milk if it was a single use item, but let's keep it as tool. + } + } else { + this.scene.events.emit('show-floating-text', { + x: npc.sprite.x, y: npc.sprite.y - 40, text: 'Not ready...', color: '#AAAAAA' + }); + } + return; + } + if (!isAttack) npc.toggleState(); return; } diff --git a/src/systems/LegacySystem.js b/src/systems/LegacySystem.js index 2c0692a..49c1549 100644 --- a/src/systems/LegacySystem.js +++ b/src/systems/LegacySystem.js @@ -1,44 +1,144 @@ class LegacySystem { constructor(scene) { this.scene = scene; - this.worldAge = 0; // Days passed - this.generation = 1; - this.currentAge = 18; // Protagonist age - this.family = { - partner: null, - children: [] + // AGE & GENERATION + this.generation = 1; + this.birthDay = 1; // Global Day count when current char was born + this.currentAge = 18; // Start at 18 + + // FAMILY + this.isMarried = false; + this.spouseName = null; + this.childName = null; + this.childAge = 0; + + // FRACTIONS / REPUTATION + this.reputation = { + survivors: 0, // Human NPCs + mutants: -50, // Starts hostile + zombies: 0 // Handled by Hybrid Skill, but we can track here too }; - console.log('⏳ LegacySystem: Initialized (Gen ' + this.generation + ')'); + // Listen for Day Change logic + this.scene.events.on('update', (time, delta) => this.update(time, delta)); } - // Call daily - advanceDay() { - this.worldAge++; - // Age Logic - if (this.worldAge % 365 === 0) { - this.currentAge++; - console.log('🎂 Birthday! Now age:', this.currentAge); + update(time, delta) { + if (!this.scene.weatherSystem) return; + + const currentDay = this.scene.weatherSystem.dayCount; + + // Calculate Age + // 1 Year = 28 Days (4 seasons * 7 days). + const daysAlive = Math.max(0, currentDay - this.birthDay); + const yearsAlive = Math.floor(daysAlive / 28); + + // Don't spam update + if (this.currentAge !== 18 + yearsAlive) { + this.currentAge = 18 + yearsAlive; + this.scene.events.emit('update-age-ui', { gen: this.generation, age: this.currentAge }); + } + + // Child Growth + if (this.childName && currentDay % 28 === 0) { + // Child ages up logic could go here } } - marry(npcId) { - this.family.partner = npcId; - console.log('💍 Married to:', npcId); - } + // --- REPUTATION --- + modifyReputation(fraction, amount) { + if (this.reputation[fraction] !== undefined) { + this.reputation[fraction] += amount; + // Clamp -100 to 100 + this.reputation[fraction] = Phaser.Math.Clamp(this.reputation[fraction], -100, 100); - haveChild(name) { - if (this.family.children.length < 2) { - this.family.children.push({ name: name, age: 0 }); - console.log('👶 New Child:', name); + if (this.scene.player) { + this.scene.events.emit('show-floating-text', { + x: this.scene.player.sprite.x, + y: this.scene.player.sprite.y - 60, + text: `Reputation (${fraction}): ${amount > 0 ? '+' : ''}${amount}`, + color: amount > 0 ? '#00FF00' : '#FF0000' + }); + } } } - dieAndInherit(heirIndex) { - console.log('⚰️ Character Died. Passing legacy...'); + // --- MARRIAGE --- + propose(npc) { + if (this.isMarried) { + this.showText("You are already married!"); + return; + } + + // Logic: Need Ring and High Rep + const hasRing = this.scene.inventorySystem && this.scene.inventorySystem.hasItem('gold_coin'); // Placeholder: use Gold Coin as "Ring" for now + // Or better: Let's create 'ring' item? For now, Gold Coins (50?). + + // Let's require 50 Gold Coins for now? Or specific item? + // Let's assume inventory checks are done before calling this, or simple check here. + + const highRep = this.reputation.survivors >= 50; + + // Temp logic: Always succeed if rep > 0 for testing? + if (highRep) { + this.isMarried = true; + this.spouseName = npc.name || "Villager"; + this.showText(`You married ${this.spouseName}!`); + } else { + this.showText("They refused... (Need 50 Rep)"); + } + } + + haveChild() { + if (!this.isMarried) return; + if (this.childName) return; + + this.childName = "Baby Jr."; + this.childAge = 0; + this.showText("A child is born!"); + } + + // --- INHERITANCE / DEATH --- + triggerInheritance() { + console.log('💀 TRIGGERING INHERITANCE...'); + + // 1. Increment Generation this.generation++; - this.currentAge = 18; // Reset age for heir - // TODO: Transfer inventory and stats + + // 2. Reset Player Stats + if (this.scene.weatherSystem) { + this.birthDay = this.scene.weatherSystem.dayCount; // Born today + } + + this.currentAge = 18; // Reset age + this.isMarried = false; + this.spouseName = null; + this.childName = null; // Clean slate (child became protagonist) + + // 3. Clear transient reputation, but keep a bit? + // Inheritance bonus: Keep 20% of rep? + this.reputation.survivors = Math.floor(this.reputation.survivors * 0.2); + this.reputation.mutants = Math.floor(this.reputation.mutants * 0.2) - 40; // Still suspicious + + this.showText(`Step Id: 760 +Generation ${this.generation} Begins!`, '#FFD700'); + + // Heal + if (this.scene.player) { + this.scene.player.hp = this.scene.player.maxHp; + this.scene.player.isDead = false; + this.scene.player.sprite.setTint(0xffffff); + this.scene.player.sprite.setAlpha(1); + } + } + + showText(msg, color = '#FFFFFF') { + const player = this.scene.player; + if (player) { + this.scene.events.emit('show-floating-text', { + x: player.sprite.x, y: player.sprite.y - 50, text: msg, color: color + }); + } } } diff --git a/src/systems/OceanSystem.js b/src/systems/OceanSystem.js new file mode 100644 index 0000000..314baa7 --- /dev/null +++ b/src/systems/OceanSystem.js @@ -0,0 +1,181 @@ +class OceanSystem { + constructor(scene) { + this.scene = scene; + + this.isDiving = false; + this.oxygen = 100; + this.maxOxygen = 100; + this.oxygenDecayRate = 10; // Oxygen lost per second + this.oxygenRegenRate = 20; // Oxygen gained per second + + this.damageTimer = 0; + + // Visual Effects + this.overlay = null; + this.bubbleParticles = null; + + // Boat + this.currentBoat = null; + this.isBoating = false; + + // Init UI Event Listener + this.scene.events.on('update', (time, delta) => { + this.update(time, delta); + this.updateBoatVisuals(); + }); + } + + update(time, delta) { + if (!this.scene.player) return; + + const playerPos = this.scene.player.getPosition(); + const tile = this.scene.terrainSystem.getTile(playerPos.x, playerPos.y); + + if (tile && tile.type && tile.type.name === 'water') { + + // Check if boating + if (this.isBoating) { + // No drowning logic if in boat + this.oxygen = this.maxOxygen; // Regaining oxygen/safe + } else { + this.enterWater(); + this.handleDiving(delta); + } + } else { + // LAND LOGIC + // If boating on land -> Beaching/Stop boating + if (this.isBoating) { + this.exitBoat(); + } + this.exitWater(); + this.handleRegen(delta); + } + + // Update UI + if (this.isDiving || this.oxygen < this.maxOxygen) { + this.scene.events.emit('update-oxygen', { current: this.oxygen, max: this.maxOxygen }); + } else { + this.scene.events.emit('hide-oxygen'); + } + } + + enterWater() { + if (this.isDiving) return; + this.isDiving = true; + console.log('🌊 Entered Water'); + + // Visuals + if (!this.overlay) { + this.overlay = this.scene.add.rectangle(0, 0, this.scene.scale.width, this.scene.scale.height, 0x0000FF, 0.3); + this.overlay.setScrollFactor(0); + this.overlay.setDepth(9000); // Above most things, below UI + } + this.overlay.setVisible(true); + + // Slow player? + // Slow player? + this.scene.player.moveSpeed = 60; // Slower in water + } + + useBoat(boatItem) { + if (this.isBoating) { + this.exitBoat(); + return; + } + + // Check if on water or near water? + // Actually, let's allow "deploying" boat anywhere, but only moving fast on water? + // OR: Only allow deploying on water tile. + + const playerPos = this.scene.player.getPosition(); + const tile = this.scene.terrainSystem.getTile(playerPos.x, playerPos.y); + + if (tile && tile.type && tile.type.name === 'water') { + this.enterBoat(playerPos.x, playerPos.y); + } else { + this.scene.events.emit('show-floating-text', { + x: this.scene.player.sprite.x, y: this.scene.player.sprite.y - 50, text: 'Must be in water!', color: '#FF0000' + }); + } + } + + enterBoat() { + this.isBoating = true; + this.isDiving = false; + if (this.overlay) this.overlay.setVisible(false); // No blue overlay on boat + + // Visual change + this.scene.player.moveSpeed = 200; // Fast speed! + + // Create boat visual under player? + // Simplified: Change player texture or add container + if (!this.currentBoat) { + this.currentBoat = this.scene.add.sprite(0, 0, 'boat'); + this.currentBoat.setDepth(this.scene.player.sprite.depth - 1); + } + this.currentBoat.setVisible(true); + + this.scene.events.emit('show-floating-text', { + x: this.scene.player.sprite.x, y: this.scene.player.sprite.y - 50, text: 'Deployed Boat!', color: '#00FFFF' + }); + } + + exitBoat() { + this.isBoating = false; + if (this.currentBoat) this.currentBoat.setVisible(false); + + // Return to normal or water speed? + // Logic in update loop will handle re-entering water state if still on water + this.scene.player.moveSpeed = 150; + } + + exitWater() { + if (!this.isDiving) return; + this.isDiving = false; + console.log('🏖️ Exited Water'); + + if (this.overlay) this.overlay.setVisible(false); + + // Restore speed + this.scene.player.moveSpeed = 150; // Normal speed + } + + handleDiving(delta) { + this.oxygen -= (this.oxygenDecayRate * delta) / 1000; + + if (this.oxygen <= 0) { + this.oxygen = 0; + this.damageTimer += delta; + if (this.damageTimer > 1000) { + this.damageTimer = 0; + this.scene.player.takeDamage(5); + this.scene.events.emit('show-floating-text', { + x: this.scene.player.sprite.x, + y: this.scene.player.sprite.y - 50, + text: 'Drowning!', + color: '#FF0000' + }); + } + } + } + + handleRegen(delta) { + if (this.oxygen < this.maxOxygen) { + this.oxygen += (this.oxygenRegenRate * delta) / 1000; + if (this.oxygen > this.maxOxygen) this.oxygen = this.maxOxygen; + } + updateBoatVisuals() { + if (this.isBoating && this.currentBoat && this.scene.player) { + this.currentBoat.x = this.scene.player.sprite.x; + this.currentBoat.y = this.scene.player.sprite.y + 10; // Slightly below + this.currentBoat.setDepth(this.scene.player.sprite.depth - 1); + + // Flip based on movement? + // Access input vector or previous pos? + // Simplified: + if (this.scene.input.keyboard.checkDown(this.scene.input.keyboard.addKey('A'), 100)) this.currentBoat.setFlipX(false); + if (this.scene.input.keyboard.checkDown(this.scene.input.keyboard.addKey('D'), 100)) this.currentBoat.setFlipX(true); + } + } + } +} diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js index b4a577e..8318edb 100644 --- a/src/systems/TerrainSystem.js +++ b/src/systems/TerrainSystem.js @@ -277,7 +277,12 @@ class TerrainSystem { if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) { terrainType = this.terrainTypes.GRASS_FULL; } else { - if (elevation < -0.6) terrainType = this.terrainTypes.WATER; + if (elevation < -0.6) terrainType = this.terrainTypes.WATER; // Deep Ocean + else if (elevation < -0.4 && Math.random() < 0.2) { + // ISOLATED ISLANDS (Nodes) + // If we are in deep water but get random bump -> Island + terrainType = this.terrainTypes.SAND; + } else if (elevation > 0.1) terrainType = this.terrainTypes.SAND; else if (elevation > 0.2) terrainType = this.terrainTypes.GRASS_FULL; else if (elevation > 0.3) terrainType = this.terrainTypes.GRASS_TOP; diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js index 4a4b1c0..a8e30cc 100644 --- a/src/utils/TextureGenerator.js +++ b/src/utils/TextureGenerator.js @@ -229,6 +229,17 @@ class TextureGenerator { ctx.beginPath(); ctx.arc(32, 16, 4, 0, Math.PI * 2); ctx.fill(); } + } else if (name === 'stable') { + // Stable + ctx.fillStyle = '#8B4513'; // Wood + ctx.fillRect(0, 16, 32, 16); + ctx.fillStyle = '#A0522D'; // Roof + ctx.beginPath(); + ctx.moveTo(0, 16); ctx.lineTo(16, 0); ctx.lineTo(32, 16); + ctx.fill(); + // Door + ctx.fillStyle = '#000'; + ctx.fillRect(10, 20, 12, 12); } else { // Generic box for others ctx.fillStyle = '#8B4513'; @@ -629,6 +640,62 @@ class TextureGenerator { drawBlock(ctx, 10, 8, '#696969'); c.refresh(); } + + // 6. BUCKET (Empty) + { + const { c, ctx } = refresh('item_bucket'); + // Body + drawBlock(ctx, 12, 14, '#C0C0C0'); + drawBlock(ctx, 14, 14, '#C0C0C0'); + drawBlock(ctx, 16, 14, '#C0C0C0'); + + drawBlock(ctx, 11, 12, '#C0C0C0'); + drawBlock(ctx, 17, 12, '#C0C0C0'); + + drawBlock(ctx, 10, 10, '#C0C0C0'); + drawBlock(ctx, 18, 10, '#C0C0C0'); + + // Handle + ctx.fillStyle = '#696969'; + ctx.fillRect(10, 6, 12, 2); + + c.refresh(); + } + + // 7. BUCKET_MILK + { + const { c, ctx } = refresh('item_bucket_milk'); + // Body (Same as bucket) + drawBlock(ctx, 12, 14, '#C0C0C0'); + drawBlock(ctx, 14, 14, '#C0C0C0'); + drawBlock(ctx, 16, 14, '#C0C0C0'); + drawBlock(ctx, 11, 12, '#C0C0C0'); + drawBlock(ctx, 17, 12, '#C0C0C0'); + + // Milk Content + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(12, 10, 8, 4); + + c.refresh(); + } + + // 8. BUCKET_MILK_GLOWING + { + const { c, ctx } = refresh('item_bucket_milk_glowing'); + // Body + drawBlock(ctx, 12, 14, '#C0C0C0'); + drawBlock(ctx, 14, 14, '#C0C0C0'); + drawBlock(ctx, 16, 14, '#C0C0C0'); + drawBlock(ctx, 11, 12, '#C0C0C0'); + drawBlock(ctx, 17, 12, '#C0C0C0'); + + // Glowing Milk Content + ctx.fillStyle = '#00FF00'; + ctx.fillRect(12, 10, 8, 4); + + c.refresh(); + } + } static createItemSprites(scene) { @@ -643,7 +710,10 @@ class TextureGenerator { { name: 'item_bone', color: '#F5F5DC' }, // Beige { name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos) { name: 'item_chip', color: '#00CED1' }, // DarkTurquoise (elektronski chip) - { name: 'artefact_old', color: '#8B4513' } // Ancient Pot (Brown) + { name: 'artefact_old', color: '#8B4513' }, // Ancient Pot (Brown) + { name: 'milk', color: '#FFFFFF' }, // White + { name: 'glowing_milk', color: '#00FF00' }, // Green + { name: 'animal_feed', color: '#F4A460' } // Sandy Brown (Feed) ]; items.forEach(item => { const it = typeof item === 'string' ? item : item.name; @@ -778,6 +848,7 @@ class TextureGenerator { TextureGenerator.createToolSprites(this.scene); TextureGenerator.createItemSprites(this.scene); TextureGenerator.createScooterSprite(this.scene, 'scooter', false); + TextureGenerator.createBoatSprite(this.scene, 'boat'); TextureGenerator.createScooterSprite(this.scene, 'scooter_broken', true); TextureGenerator.createAnimatedWaterSprite(this.scene); TextureGenerator.createPathStoneSprite(this.scene, 'path_stone'); @@ -954,6 +1025,34 @@ class TextureGenerator { canvas.refresh(); } + static createBoatSprite(scene, key = 'boat') { + if (scene.textures.exists(key)) return; + const canvas = scene.textures.createCanvas(key, 48, 24); + const ctx = canvas.getContext(); + ctx.clearRect(0, 0, 48, 24); + + // Hull (Wood) + ctx.fillStyle = '#8B4513'; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(48, 0); + ctx.lineTo(40, 24); + ctx.lineTo(8, 24); + ctx.closePath(); + ctx.fill(); + + // Rim + ctx.strokeStyle = '#603010'; + ctx.lineWidth = 2; + ctx.strokeRect(2, 2, 44, 20); + + // Seat + ctx.fillStyle = '#603010'; + ctx.fillRect(16, 8, 16, 4); + + canvas.refresh(); + } + static createChickenSprite(scene, key, activeMutation) { if (scene.textures.exists(key)) return; const canvas = scene.textures.createCanvas(key, 24, 24);