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);