From e49f567831d86a61093edfceb23f9007d56ab8da Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Mon, 8 Dec 2025 01:39:39 +0100 Subject: [PATCH] farma updejt --- DESIGN_DOC.md | 53 +++++++++ OPEN_WORLD_PLAN.md | 46 ++++++++ TASKS.md | 19 +++- src/scenes/GameScene.js | 26 ++++- src/scenes/UIScene.js | 47 ++++---- src/systems/Antigravity.js | 78 +++++++++++++ src/systems/FarmingSystem.js | 135 ++++++++++++++--------- src/systems/InteractionSystem.js | 5 + src/systems/SoundManager.js | 20 ++++ src/systems/StatsSystem.js | 68 ++++++++++++ src/systems/TerrainSystem.js | 17 ++- src/utils/TextureGenerator.js | 181 +++++++++++++++++++++++++++---- 12 files changed, 596 insertions(+), 99 deletions(-) create mode 100644 DESIGN_DOC.md create mode 100644 OPEN_WORLD_PLAN.md diff --git a/DESIGN_DOC.md b/DESIGN_DOC.md new file mode 100644 index 0000000..c8fff85 --- /dev/null +++ b/DESIGN_DOC.md @@ -0,0 +1,53 @@ +# 🚜 NovaFarma - Design Document & Pillars +*Konceptualna zasnova in arhitekturna pravila (Inspired by Stardew Valley)* + +## 1. Core Pillars (Glavni stebri) + +### 🎨 Vizualni Stil: Pixel Art Nostalgija +- **Tehnika:** Ročno narisan (ali proceduralno generiran) Pixel Art. +- **Renderiranje:** Vedno uporabi **`NEAREST`** neighbor filtriranje. Nobenega 'blur-a'. +- **Snap-to-Grid:** Sprite-i se morajo poravnati na piksle (Math.round), da preprečimo 'sub-pixel' napake. + +### 📐 Perspektiva: 2.5D Iluzija +- **Trik:** Igra uporablja 2D mrežo, a z navpičnim zamikom ustvarja iluzijo višine. +- **Grids:** + 1. **Ground Layer (Tla):** Ploščice (Tiles), po katerih se hodi. So 'ravne'. + 2. **Object Layer (Predmeti):** Drevesa, zgradbe, igralec. Imajo 'višino'. + +### ↕️ Depth Sorting (Y-Sort) +To je srce 2.5D motorja (`Antigravity Engine`). +- Objekti se rišejo v vrstnem redu glede na njihovo **Y-koordinato** na zaslonu. +- **Pravilo:** `Depth = BaseLayer + Sprite.y`. +- To omogoča, da igralec hodi "za" drevesom in "pred" ograjo naravno. + +## 2. Arhitektura Motorja (Antigravity Engine) + +Namesto MonoGame (C#) uporabljamo **Phaser 3 (JS)**, vendar s podobno strukturo: + +### 🗺️ Tileset System (`TerrainSystem.js`) +Svet je razdeljen na dva nivoja podatkov: +1. **TileMap (Matrika):** + - Hrani tip tal (Trava, Zemlja, Voda). + - Določa osnovno prehodnost (Voda = neprehodno). +2. **DecorationMap (Objekti):** + - Hrani entitete na koordinatah (Drevo na 20,20). + - Ti objekti so neodvisni Sprit-i z lastno logiko (Health, Growth). + +### 🔄 Game Loop (`Antigravity.Update`) +1. **Input:** Preberi vnose. +2. **Logic:** Premakni entitete, preveri kolizije (hitboxi). +3. **Sorting:** `depthSortSprites()` poskrbi za pravilno risanje. +4. **Render:** Phaser nariše sceno. + +## 3. Gameplay Mechanics + +### Kmetovanje & Nabiralništvo +- Interakcija temelji na **Grid Selection** (izbira kvadratka). +- Orodja delujejo na principu "Active Tile". + +### RPG Elementi +- NPC-ji imajo urnike in obnašanje (State Machines). +- Ekonomija temelji na prodaji pridelkov. + +--- +*Ta dokument služi kot referenca za preobrazbo NovaFarme v polnokrven 2.5D RPG.* diff --git a/OPEN_WORLD_PLAN.md b/OPEN_WORLD_PLAN.md new file mode 100644 index 0000000..35ac8f2 --- /dev/null +++ b/OPEN_WORLD_PLAN.md @@ -0,0 +1,46 @@ +# 🌍 Open World Strategy Plan +*Roadmap za prehod iz statične mape v neskončen odprt svet (za razliko od Stardew Valley con).* + +## 1. Konceptualna Razlika +- **Stardew Valley:** Ima ločene "sobe" (Farm, Town, Beach). Ko greš čez rob, se naloži nova mapa (Loading Screen). +- **NovaFarma (Cilj):** **Seamless Open World**. Brez nalaganja. Igralec hodi v katerokoli smer in svet se generira sproti. + +## 2. Tehnični Izziv: Chunk System (Koščki Sveta) +Ker računalnik ne more hraniti neskončne mape v spominu, moramo svet razdeliti na **Chunke** (npr. 16x16 ploščic). + +### 📐 Arhitektura +1. **Chunk Manager (`WorldSystem.js`):** + - Spremlja pozicijo igralca (npr. Chunk X: 5, Y: 10). + - **Active Window:** Naloži samo 9 chunkov okoli igralca (Center + 8 sosedov). + - **Generation:** Če chunk še ne obstaja, ga generira s Perlin Noise funkcijo (deterministično - isti seed = isti svet). + - **Unloading:** Chunke, ki so daleč stran, odstrani iz spomina (vendar shrani spremembe!). + +2. **Perzistenca (Shranjevanje):** + - Težava: Če posekam drevo v Chunku (100, 100) in grem stran, ter se vrnem, mora biti drevo še vedno podrto. + - Rešitev: `Delta Compression`. Ne shranjujemo celega chunka, ampak samo **razlike** (npr. `{ "100,100": { removedDecor: true } }`). + +## 3. Generacija Sveta (Biomi) +Za razliko od trenutne 100x100 mape, mora Open World imeti strukturo na velikem nivoju. + +### 🌡️ Biome Map (Noise Layer 2) +Uporabimo drugi Perlin Noise z zelo nizko frekvenco (velik zoom) za določanje biomov/temperature. +- **Noise < 0.3:** ❄️ Snow Biome (Zalejeno, Jelke) +- **Noise 0.3 - 0.7:** 🌲 Temperate (Trava, Hrast - trenutni stil) +- **Noise > 0.7:** 🌵 Desert (Pesek, Kaktusi) + +## 4. Implementacijskih Koraki + +### Faza 1: Refactor TerrainSystem na Chunke +- Namesto `this.tiles[100][100]`, uporabimo `this.chunks = Map`. +- `getTile(x, y)` mora preračunati: `chunkX = floor(x/16)`, `localX = x % 16`. + +### Faza 2: Dynamic Loading +- V `update()` zanki preverjamo ali je igralec prečkal mejo chunka. +- Če da → sproži nalaganje novih sosedov. + +### Faza 3: "Infinite" Koordinatni Sistem +- Ker JS nima težav z velikimi števili do 9 kintilijonov, `Floating Origin` verjetno še ni nujen, dokler ne gremo ekstremno daleč. + +--- +**Zaključek:** +To je velik tehnični preskok. Trenutna `100x100` mapa je "en velik chunk". Prvi korak je razbitje te logike na manjše enote. diff --git a/TASKS.md b/TASKS.md index bd22ed8..828e56e 100644 --- a/TASKS.md +++ b/TASKS.md @@ -147,6 +147,21 @@ Fokus na igralnost, loot in napredovanje. - [ ] Roads connecting Farm and City - [ ] Signposts +## 🧬 Phase 9: Antigravity Transformation (Design Overhaul) +Implementacija arhitekturnih stebrov po zgledu Stardew Valley. + +- [x] **Core Engine Setup** (`Antigravity.js`) + - [x] Global Namespace & Config + - [x] Centralized Update Loop +- [x] **Rendering Pipeline** + - [x] NEAREST Filter enforce + - [x] Tile Padding/Extrude (No bleeding) + - [x] Pixel-Perfect Math.round positioning +- [x] **Depth Sorting v2.0** + - [x] Y-Sorting za vse entitete +- [ ] **Concept Integration** + - [ ] Poenotenje vseh sistemov pod `Antigravity` namespace (Future Refactor) + --- -**PROJECT STATUS: PHASE 8 STARTED** 🚧 -*Last Updated: 2025-12-07 (Pathfinding & Assets Update)* +**PROJECT STATUS: TRANSFORMING...** 🦋 +*Last Updated: 2025-12-08 (Antigravity Engine Integration)* diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index b396535..a50e305 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -131,9 +131,10 @@ class GameScene extends Phaser.Scene { this.player = new Player(this, 50, 50, this.terrainOffsetX, this.terrainOffsetY); // Dodaj 3 NPCje (Mixed) + // Dodaj 3 NPCje (Mixed) - Removed zombie console.log('🧟 Initializing NPCs...'); - const npcTypes = ['zombie', 'villager', 'merchant']; - for (let i = 0; i < 3; i++) { + const npcTypes = ['villager', 'merchant']; + for (let i = 0; i < npcTypes.length; i++) { const randomX = Phaser.Math.Between(40, 60); // Closer to center const randomY = Phaser.Math.Between(40, 60); console.log(`👤 Spawning NPC type: ${npcTypes[i]} at (${randomX}, ${randomY})`); @@ -141,13 +142,15 @@ class GameScene extends Phaser.Scene { this.npcs.push(npc); } - // Dodaj 10 dodatnih Zombijev! + // Dodaj 10 dodatnih Zombijev! - REMOVED BY REQUEST + /* for (let i = 0; i < 10; i++) { const randomX = Phaser.Math.Between(10, 90); const randomY = Phaser.Math.Between(10, 90); const zombie = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, 'zombie'); this.npcs.push(zombie); } + */ // ELITE ZOMBIES v City območju (65,65 ± 15) console.log('👹 Spawning ELITE ZOMBIES in City...'); @@ -223,6 +226,9 @@ class GameScene extends Phaser.Scene { .setScrollFactor(0).setDepth(10000); console.log('✅ GameScene ready - FAZA 20 (Full Features)!'); + + // Start Engine + this.Antigravity_Start(); } setupCamera() { @@ -512,6 +518,20 @@ class GameScene extends Phaser.Scene { // ANTIGRAVITY ENGINE UPDATE // ======================================================== + Antigravity_Start() { + console.log('🚀 Starting Antigravity Engine...'); + + if (window.Antigravity) { + // Camera Setup + if (this.player && this.player.sprite) { + window.Antigravity.Camera.follow(this, this.player.sprite); + } + + // ZOOM SETUP - 0.75 za "Open World" pregled + window.Antigravity.Camera.setZoom(this, 0.75); + } + } + Antigravity_Update(delta) { // Globalni update klic if (window.Antigravity) { diff --git a/src/scenes/UIScene.js b/src/scenes/UIScene.js index fb1b930..11c6d3d 100644 --- a/src/scenes/UIScene.js +++ b/src/scenes/UIScene.js @@ -15,7 +15,8 @@ class UIScene extends Phaser.Scene { this.overlayGraphics = this.add.graphics(); this.overlayGraphics.setDepth(-1000); // Behind UI elements - this.createStatusBars(); + this.drawUI(); + this.createInventoryBar(); this.createGoldDisplay(); this.createVirtualJoystick(); @@ -176,36 +177,37 @@ class UIScene extends Phaser.Scene { } } - createStatusBars() { + drawUI() { const x = 20; const y = 20; - const width = 200; - const height = 20; + const width = 150; // Zmanjšana širina (300 -> 150) + const height = 15; // Zmanjšana višina (30 -> 15) const padding = 10; - // Style - const boxStyle = { - fillStyle: { color: 0x000000, alpha: 0.5 }, - lineStyle: { width: 2, color: 0xffffff, alpha: 0.8 } - }; - // 1. Health Bar - this.add.text(x, y - 5, 'HP', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' }); - this.healthBar = this.createBar(x + 30, y, width, height, 0xff0000); - this.setBarValue(this.healthBar, 100); + this.healthBar = this.drawBar(x, y, width, height, 0xff0000, 100, 'HP'); // 2. Hunger Bar - this.add.text(x, y + height + padding - 5, 'HUN', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' }); - this.hungerBar = this.createBar(x + 30, y + height + padding, width, height, 0xff8800); - this.setBarValue(this.hungerBar, 80); + this.hungerBar = this.drawBar(x, y + height + padding, width, height, 0xff8800, 80, 'HUN'); // 3. Thirst Bar - this.add.text(x, y + (height + padding) * 2 - 5, 'H2O', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' }); - this.thirstBar = this.createBar(x + 30, y + (height + padding) * 2, width, height, 0x0088ff); - this.setBarValue(this.thirstBar, 90); + this.thirstBar = this.drawBar(x, y + (height + padding) * 2, width, height, 0x0088ff, 90, 'H2O'); + + // 4. XP Bar + this.XPBar = this.drawBar(x, y + (height + padding) * 3, width, height, 0xFFD700, 0, 'XP'); + + // 5. Level Display + this.LevelDisplay = this.add.text(x, y + (height + padding) * 4, 'LV: 1', { + fontSize: '18px', fontFamily: 'Courier New', fill: '#FFD700', fontStyle: 'bold' + }); } - createBar(x, y, width, height, color) { + drawBar(x, y, width, height, color, initialPercent = 100, label = '') { + // Label + if (label) { + this.add.text(x, y - 12, label, { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' }); + } + // Background const bg = this.add.graphics(); bg.fillStyle(0x000000, 0.5); @@ -215,8 +217,11 @@ class UIScene extends Phaser.Scene { // Fill const fill = this.add.graphics(); + // Initial draw fill.fillStyle(color, 1); - fill.fillRect(x + 2, y + 2, width - 4, height - 4); + const maxWidth = width - 4; + const currentWidth = (maxWidth * initialPercent) / 100; + fill.fillRect(x + 2, y + 2, currentWidth, height - 4); return { bg, fill, x, y, width, height, color }; } diff --git a/src/systems/Antigravity.js b/src/systems/Antigravity.js index 247b8b6..a03daff 100644 --- a/src/systems/Antigravity.js +++ b/src/systems/Antigravity.js @@ -43,6 +43,84 @@ window.Antigravity = { } }, + Camera: { + /** + * Nastavi kamero, da sledi tarči + * @param {Phaser.Scene} scene + * @param {Phaser.GameObjects.GameObject} target + */ + follow: function (scene, target) { + if (scene.cameras && scene.cameras.main && target) { + // Uporabimo lerp za gladko sledenje (0.1, 0.1) + scene.cameras.main.startFollow(target, true, 0.1, 0.1); + console.log('📷 Antigravity Camera: Following target'); + } + }, + + /** + * Nastavi zoom stopnjo kamere + * @param {Phaser.Scene} scene + * @param {number} zoomLevel + */ + setZoom: function (scene, zoomLevel) { + if (scene.cameras && scene.cameras.main) { + scene.cameras.main.setZoom(zoomLevel); + console.log(`🔍 Antigravity Camera: Zoom set to ${zoomLevel}`); + } + } + }, + + UI: { + showMessage: function (scene, message, color = '#ffffff') { + if (scene.events && scene.player) { + scene.events.emit('show-floating-text', { + x: scene.player.x, + y: scene.player.y - 60, + text: message, + color: color + }); + } + }, + + setText: function (scene, elementId, text) { + const ui = scene.scene.get('UIScene'); + if (ui && ui[elementId]) { + ui[elementId].setText(text); + } + }, + + setBarValue: function (scene, elementId, percent) { + const ui = scene.scene.get('UIScene'); + if (ui && ui[elementId] && ui.setBarValue) { + ui.setBarValue(ui[elementId], percent); + } + }, + + drawRectangle: function (scene, x, y, width, height, color = 0xffffff, alpha = 1, isHUD = false) { + const rect = scene.add.rectangle(x, y, width, height, color, alpha); + if (isHUD) { + rect.setScrollFactor(0); + rect.setOrigin(0, 0); + rect.setDepth(10000); + } + return rect; + }, + + strokeRectangle: function (scene, x, y, width, height, color = 0xffffff, thickness = 2, isHUD = false) { + const g = scene.add.graphics(); + g.lineStyle(thickness, color, 1); + + if (isHUD) { + g.strokeRect(x, y, width, height); + g.setScrollFactor(0); + g.setDepth(10000); + } else { + g.strokeRect(x - width / 2, y - height / 2, width, height); + } + return g; + } + }, + /** * Glavni update loop za Engine * @param {Phaser.Scene} scene diff --git a/src/systems/FarmingSystem.js b/src/systems/FarmingSystem.js index 6974f92..6866e5e 100644 --- a/src/systems/FarmingSystem.js +++ b/src/systems/FarmingSystem.js @@ -1,10 +1,30 @@ +const CROP_DATA = { + 'wheat': { + name: 'Wheat', + seedItem: 'seeds_wheat', + harvestItem: 'wheat', + stages: 4, + growthTime: 10, // Seconds per stage (Total 30s) + regrow: false, + color: 0xffdd00 + }, + 'corn': { + name: 'Corn', + seedItem: 'seeds_corn', + harvestItem: 'corn', + stages: 4, + growthTime: 15, // Slower (Total 45s) + regrow: true, // Corn regrows! + regrowStage: 2, // Goes back to stage 2 + color: 0xffaa00 + } +}; + class FarmingSystem { constructor(scene) { this.scene = scene; this.growthTimer = 0; - this.growthTickRate = 5000; // Check growth every 5 seconds (real time) - // Or better: based on TimeSystem days? - // For fast testing: rapid growth. + this.growthTickRate = 1000; } // Called by InteractionSystem @@ -14,40 +34,45 @@ class FarmingSystem { if (!tile) return false; - // 1. HARVEST (Right click or just click ripe crop?) - // Let's say if it has crop and it is ripe, harvest it regardless of tool. + // 1. HARVEST if (tile.hasCrop) { const crop = terrain.cropsMap.get(`${gridX},${gridY}`); - console.log('🌾 Check harvest:', crop); - if (crop && crop.stage === 4) { + if (crop && crop.stage === CROP_DATA[crop.type].stages) { this.harvest(gridX, gridY); return true; } } - // 2. TILLING (Requires Hoe) + // 2. TILLING if (toolType === 'hoe') { const typeName = tile.type.name || tile.type; if (typeName.includes('grass') || typeName === 'dirt') { if (!tile.hasDecoration && !tile.hasCrop) { - console.log('🚜 Tilling soil...'); - terrain.setTileType(gridX, gridY, 'farmland'); // This sets it to string 'farmland' usually? or object? Assuming method handles it. - // Play sound + terrain.setTileType(gridX, gridY, 'farmland'); + if (this.scene.soundManager) this.scene.soundManager.playDig(); return true; } } } - // 3. PLANTING (Requires Seeds) - if (toolType === 'seeds') { + // 3. PLANTING + // Check if tool is a seed + const isSeed = toolType.startsWith('seeds_') || toolType === 'seeds'; + + if (isSeed) { const typeName = tile.type.name || tile.type; if (typeName === 'farmland' && !tile.hasCrop && !tile.hasDecoration) { - console.log('🌱 Planting seeds...'); - this.plant(gridX, gridY); + // Determine crop type from seed name + // "seeds_corn" -> "corn", "seeds" -> "wheat" (default) + let cropType = 'wheat'; + if (toolType === 'seeds_corn') cropType = 'corn'; + if (toolType === 'seeds_wheat') cropType = 'wheat'; - // Remove 1 seed from inventory + this.plant(gridX, gridY, cropType); + + // Consumption if (this.scene.inventorySystem) { - this.scene.inventorySystem.removeItem('seeds', 1); + this.scene.inventorySystem.removeItem(toolType, 1); } return true; } @@ -56,57 +81,72 @@ class FarmingSystem { return false; } - plant(x, y) { + plant(x, y, type = 'wheat') { const terrain = this.scene.terrainSystem; + const data = CROP_DATA[type]; + const cropData = { gridX: x, gridY: y, - stage: 1, // Seeds - type: 'wheat', // Default for now + stage: 1, + type: type, timer: 0, - maxTime: 10 // Seconds per stage? + maxTime: data.growthTime }; terrain.addCrop(x, y, cropData); - // Plant Sound if (this.scene.soundManager) { this.scene.soundManager.playPlant(); } - // Quest Tracking if (this.scene.questSystem) this.scene.questSystem.trackAction('plant'); + console.log(`🌱 Planted ${type} at ${x},${y}`); } harvest(x, y) { const terrain = this.scene.terrainSystem; - console.log('🌾 Harvesting!'); + const crop = terrain.cropsMap.get(`${x},${y}`); + if (!crop) return; - // Harvest Sound + const data = CROP_DATA[crop.type]; + + // Sound if (this.scene.soundManager) { this.scene.soundManager.playHarvest(); } - // Spawn loot + // Loot if (this.scene.interactionSystem) { - this.scene.interactionSystem.spawnLoot(x, y, 'wheat'); - this.scene.interactionSystem.spawnLoot(x, y, 'seeds'); // Return seeds - // 50% chance for extra seeds - if (Math.random() > 0.5) this.scene.interactionSystem.spawnLoot(x, y, 'seeds'); + this.scene.interactionSystem.spawnLoot(x, y, data.harvestItem, 1); + + // Chance for seeds + if (Math.random() > 0.4) { + this.scene.interactionSystem.spawnLoot(x, y, data.seedItem, 1); + } + + // Extra harvest chance + if (Math.random() > 0.5) { + this.scene.interactionSystem.spawnLoot(x, y, data.harvestItem, 1); + } + + // XP Gain + if (this.scene.statsSystem) { + this.scene.statsSystem.addXP(data.harvestItem === 'corn' ? 15 : 10); + } } - // Remove crop - terrain.removeCrop(x, y); - - // Revert to dirt? Or keep farmland? Usually keeps farmland. + // Regrow or Destroy + if (data.regrow) { + crop.stage = data.regrowStage; + crop.timer = 0; + terrain.updateCropVisual(x, y, crop.stage); + console.log(`🔄 ${crop.type} regrowing...`); + } else { + terrain.removeCrop(x, y); + } } update(delta) { - // Growth Logic - // Iterate all crops? Expensive? - // Better: Random tick like Minecraft or list iteration. - // Since we have cropsMap, iteration is easy. - - // Only run every 1 second (1000ms) to save PERF this.growthTimer += delta; if (this.growthTimer < 1000) return; const secondsPassed = this.growthTimer / 1000; @@ -116,20 +156,15 @@ class FarmingSystem { if (!terrain) return; for (const [key, crop] of terrain.cropsMap) { - if (crop.stage < 4) { + const data = CROP_DATA[crop.type]; + if (!data) continue; + + if (crop.stage < data.stages) { crop.timer += secondsPassed; - - // Growth thresholds (fast for testing) - // Stage 1 -> 2: 5s - // Stage 2 -> 3: 10s - // Stage 3 -> 4: 15s - const needed = 5; - - if (crop.timer >= needed) { + if (crop.timer >= data.growthTime) { crop.stage++; crop.timer = 0; terrain.updateCropVisual(crop.gridX, crop.gridY, crop.stage); - // Particle effect? } } } diff --git a/src/systems/InteractionSystem.js b/src/systems/InteractionSystem.js index 62c9f86..26b7856 100644 --- a/src/systems/InteractionSystem.js +++ b/src/systems/InteractionSystem.js @@ -209,6 +209,11 @@ class InteractionSystem { } if (decor.hp <= 0) { + // XP REWARD (Requested by Logic) + if (this.scene.statsSystem) { + this.scene.statsSystem.addXP(5); + } + const prevType = decor.type; // Logic: If Tree (and not sapling), turn into Sapling immediately (Regrowth) diff --git a/src/systems/SoundManager.js b/src/systems/SoundManager.js index 2f2cbc6..e6105e0 100644 --- a/src/systems/SoundManager.js +++ b/src/systems/SoundManager.js @@ -26,6 +26,8 @@ class SoundManager { this.beepHarvest(); } else if (key === 'build') { this.beepBuild(); + } else if (key === 'dig') { + this.beepDig(); } } } @@ -297,10 +299,28 @@ class SoundManager { playHarvest() { this.playSFX('harvest'); } playBuild() { this.playSFX('build'); } playPickup() { this.playSFX('pickup'); } + playDig() { this.playSFX('dig'); } // Dodano playAttack() { this.beepAttack(); } playHit() { this.beepHit(); } playFootstep() { this.beepFootstep(); } playDeath() { this.beepDeath(); } playRainSound() { this.startRainNoise(); } stopRainSound() { this.stopRainNoise(); } + + beepDig() { + if (!this.scene.sound.context) return; + const ctx = this.scene.sound.context; + const osc = ctx.createOscillator(); + const gain = ctx.createGain(); + osc.connect(gain); + gain.connect(ctx.destination); + // Low "thud" for dirt interaction + osc.frequency.setValueAtTime(80, ctx.currentTime); + osc.frequency.exponentialRampToValueAtTime(10, ctx.currentTime + 0.15); + osc.type = 'triangle'; + gain.gain.setValueAtTime(0.2, ctx.currentTime); + gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); + osc.start(); + osc.stop(ctx.currentTime + 0.15); + } } diff --git a/src/systems/StatsSystem.js b/src/systems/StatsSystem.js index a14f4b5..8c794b3 100644 --- a/src/systems/StatsSystem.js +++ b/src/systems/StatsSystem.js @@ -1,7 +1,18 @@ +// ======================================================== +// KONSTANTE IN IGRALČEVI PODATKI +// ======================================================== +const XP_REQUIRED_BASE = 100; // Osnovni XP, potrebni za LVL 2 +const XP_GROWTH_FACTOR = 1.5; // Za vsak LVL potrebujete 1.5x več XP + class StatsSystem { constructor(scene) { this.scene = scene; + // Leveling System + this.currentLevel = 1; + this.currentXP = 0; + this.xpToNextLevel = XP_REQUIRED_BASE; + // Stats this.health = 100; this.maxHealth = 100; @@ -130,6 +141,18 @@ class StatsSystem { if (uiScene.hungerBar) uiScene.setBarValue(uiScene.hungerBar, this.hunger); if (uiScene.thirstBar) uiScene.setBarValue(uiScene.thirstBar, this.thirst); } + this.updateLevelUI(); + } + + updateLevelUI() { + const xpPercent = (this.currentXP / this.xpToNextLevel) * 100; + const levelText = `LV: ${this.currentLevel}`; + + // Uporaba Antigravity Engine UI klicev, kot zahtevano + if (window.Antigravity && window.Antigravity.UI) { + window.Antigravity.UI.setText(this.scene, 'LevelDisplay', levelText); + window.Antigravity.UI.setBarValue(this.scene, 'XPBar', xpPercent); + } } // Friendship System @@ -149,4 +172,49 @@ class StatsSystem { this.friendship[npcType] = amount; } } + + // ======================================================== + // LEVELING SYSTEM + // ======================================================== + + addXP(amount) { + this.currentXP += amount; + + // 1. Preverimo, ali je igralec pripravljen za Level Up + while (this.currentXP >= this.xpToNextLevel) { + this.levelUp(); + } + + this.updateUI(); + } + + levelUp() { + // Povečamo Level + this.currentLevel++; + + // Izračunamo nov XP prag + this.xpToNextLevel = Math.floor(this.xpToNextLevel * XP_GROWTH_FACTOR); + + // Preostanek XP prenesemo v nov Level + this.currentXP = this.currentXP - (this.xpToNextLevel / XP_GROWTH_FACTOR); + + // Bonus Stats + this.maxHealth += 10; + this.health = this.maxHealth; + this.maxHunger += 5; + this.maxThirst += 5; + this.hunger = this.maxHunger; + this.thirst = this.maxThirst; + + // Vizualni učinek / Prikaz sporočila + console.log(`🎉 LEVEL UP! Dosežen level ${this.currentLevel}!`); + + if (window.Antigravity && window.Antigravity.UI) { + window.Antigravity.UI.showMessage( + this.scene, + `LEVEL UP! Dosežen level ${this.currentLevel}!`, + '#FFD700' + ); + } + } } diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js index 3a20653..a45601d 100644 --- a/src/systems/TerrainSystem.js +++ b/src/systems/TerrainSystem.js @@ -279,10 +279,10 @@ class TerrainSystem { }; // Place Trees dynamically during generation - this.placeTree(x, y, terrainType.name); + // this.placeTree(x, y, terrainType.name); // Place Rocks dynamically - this.placeRock(x, y, terrainType.name); + // this.placeRock(x, y, terrainType.name); } } @@ -699,7 +699,18 @@ class TerrainSystem { const sprite = this.cropPool.get(); const screenPos = this.iso.toScreen(x, y); sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset)); - sprite.setTexture(`crop_stage_${crop.stage}`); + + const cropType = crop.type || 'wheat'; + // Če je wheat, uporabimo 'crop_stage_' za nazaj združljivost s TextureGeneratorjem? + // TextureGenerator dela 'crop_stage_X'. + // Če dodam 'wheat_stage_X', moram posodobiti TextureGenerator. + // Za zdaj: + let textureKey = `crop_stage_${crop.stage}`; + if (cropType === 'corn') textureKey = `corn_stage_${crop.stage}`; + if (cropType === 'wheat' && this.scene.textures.exists('wheat_stage_1')) textureKey = `wheat_stage_${crop.stage}`; + + sprite.setTexture(textureKey); + sprite.setOrigin(0.5, 1); // Layer Objects (da igralec hodi okoli njih) sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS)); diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js index c10bcbd..39da111 100644 --- a/src/utils/TextureGenerator.js +++ b/src/utils/TextureGenerator.js @@ -384,36 +384,138 @@ class TextureGenerator { } static createToolSprites(scene) { - // Placeholder tool generation + // --- REALISTIC TOOLS (Procedural Generation) --- + + // 1. AXE (Sekira) if (!scene.textures.exists('item_axe')) { const c = scene.textures.createCanvas('item_axe', 32, 32); - const x = c.getContext(); - x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18); - x.fillStyle = 'gray'; x.fillRect(12, 10, 8, 6); + const ctx = c.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Handle (Wood) + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 12, 4, 18); + + // Head (Metal) + ctx.fillStyle = '#708090'; // SlateGray + ctx.beginPath(); + ctx.moveTo(16, 12); + ctx.lineTo(24, 6); // Top edge + ctx.lineTo(24, 18); // Bottom edge + ctx.lineTo(16, 14); // Back to handle + ctx.fill(); + + // Edge (Sharp) + ctx.fillStyle = '#C0C0C0'; // Silver + ctx.fillRect(23, 6, 2, 12); + c.refresh(); } + + // 2. PICKAXE (Kramp) if (!scene.textures.exists('item_pickaxe')) { const c = scene.textures.createCanvas('item_pickaxe', 32, 32); - const x = c.getContext(); - x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18); - x.fillStyle = 'gray'; x.fillRect(8, 12, 16, 4); + const ctx = c.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Handle + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 10, 4, 20); + + // Head (Curved Metal) + ctx.fillStyle = '#696969'; // DimGray + ctx.beginPath(); + ctx.moveTo(6, 14); // Left tip + ctx.quadraticCurveTo(16, 4, 26, 14); // Curve over handle + ctx.lineTo(26, 16); + ctx.quadraticCurveTo(16, 8, 6, 16); + ctx.fill(); + + // Tips + ctx.fillStyle = '#DCDCDC'; + ctx.fillRect(5, 14, 2, 2); + ctx.fillRect(25, 14, 2, 2); + c.refresh(); } + + // 3. HOE (Motika) if (!scene.textures.exists('item_hoe')) { const c = scene.textures.createCanvas('item_hoe', 32, 32); - const x = c.getContext(); - x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18); // Ročaj - x.fillStyle = 'gray'; - x.fillRect(10, 8, 12, 4); // Rezilo motike + const ctx = c.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Handle + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 6, 4, 24); + + // Head + ctx.fillStyle = '#778899'; // LightSlateGray + ctx.fillRect(12, 6, 10, 4); // Top bar + ctx.fillRect(12, 6, 4, 8); // Blade down + + // Blade Edge + ctx.fillStyle = '#C0C0C0'; + ctx.fillRect(12, 12, 4, 2); + c.refresh(); } + + // 4. SWORD (Meč) if (!scene.textures.exists('item_sword')) { const c = scene.textures.createCanvas('item_sword', 32, 32); - const x = c.getContext(); - x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 14); // Ročaj - x.fillStyle = 'gray'; - x.fillRect(12, 8, 8, 12); // Rezilo meča - x.fillStyle = 'gold'; x.fillRect(14, 21, 4, 2); // Guard + const ctx = c.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Handle + ctx.fillStyle = '#8B4513'; + ctx.fillRect(15, 24, 2, 6); + // Pommel + ctx.fillStyle = '#FFD700'; // Gold + ctx.fillRect(14, 29, 4, 2); + + // Guard + ctx.fillStyle = '#FFD700'; + ctx.fillRect(11, 22, 10, 2); + + // Blade + ctx.fillStyle = '#C0C0C0'; // Silver + ctx.fillRect(14, 4, 4, 18); + + // Blood Groove / Shine + ctx.fillStyle = '#F0F8FF'; // AliceBlue + ctx.fillRect(15, 4, 2, 18); + + c.refresh(); + } + + // 5. WATERING CAN (Zalivalka) + if (!scene.textures.exists('item_watering_can')) { + const c = scene.textures.createCanvas('item_watering_can', 32, 32); + const ctx = c.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Body + ctx.fillStyle = '#A9A9A9'; // DarkGray + ctx.fillRect(8, 12, 16, 14); + + // Handle + ctx.strokeStyle = '#696969'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(8, 14); + ctx.quadraticCurveTo(4, 10, 8, 6); + ctx.lineTo(20, 6); + ctx.stroke(); + + // Spout + ctx.fillStyle = '#A9A9A9'; + ctx.beginPath(); + ctx.moveTo(24, 16); + ctx.lineTo(30, 10); + ctx.lineTo(28, 20); + ctx.fill(); + c.refresh(); } } @@ -425,6 +527,8 @@ class TextureGenerator { { name: 'stone', color: '#808080' }, // Siva { name: 'seeds', color: '#90EE90' }, // Svetlo zelena { name: 'wheat', color: '#FFD700' }, // Zlata + { name: 'corn', color: '#FFD700' }, // Zlata (Corn) + { name: 'seeds_corn', color: '#B22222' },// FireBrick seeds { name: 'item_bone', color: '#F5F5DC' }, // Beige { name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos) { name: 'item_chip', color: '#00CED1' } // DarkTurquoise (elektronski chip) @@ -464,6 +568,41 @@ class TextureGenerator { canvas.refresh(); } + static createCornSprites(scene) { + for (let i = 1; i <= 4; i++) { + const key = `corn_stage_${i}`; + if (scene.textures.exists(key)) continue; + + // Corn is taller + const canvas = scene.textures.createCanvas(key, 32, 64); + const ctx = canvas.getContext(); + ctx.clearRect(0, 0, 32, 64); + + const startY = 64; + + // Stalk + ctx.fillStyle = '#556B2F'; // DarkOliveGreen + const height = 10 + (i * 10); + ctx.fillRect(14, startY - height, 4, height); + + // Leaves + ctx.fillStyle = '#32CD32'; // LimeGreen + if (i >= 2) { + ctx.beginPath(); ctx.ellipse(10, startY - height + 10, 8, 3, 0.5, 0, Math.PI * 2); ctx.fill(); + ctx.beginPath(); ctx.ellipse(22, startY - height + 15, 8, 3, -0.5, 0, Math.PI * 2); ctx.fill(); + } + + // Cobs (Only stage 4) + if (i === 4) { + ctx.fillStyle = '#FFD700'; // Gold + ctx.fillRect(12, startY - height + 20, 4, 8); + ctx.fillRect(18, startY - height + 25, 4, 8); + } + + canvas.refresh(); + } + } + // Helper to generate ALL textures at once generateAll() { TextureGenerator.createPlayerSprite(this.scene); @@ -473,15 +612,17 @@ class TextureGenerator { TextureGenerator.createFlowerSprite(this.scene); TextureGenerator.createBushSprite(this.scene); - TextureGenerator.createSaplingSprite(this.scene, 'tree_sapling'); // Dodano - TextureGenerator.createTreeSprite(this.scene); // Volumetric - TextureGenerator.createRockSprite(this.scene); // Volumetric + TextureGenerator.createSaplingSprite(this.scene, 'tree_sapling'); + TextureGenerator.createTreeSprite(this.scene); + TextureGenerator.createRockSprite(this.scene); TextureGenerator.createCloudSprite(this.scene); + + // Crops TextureGenerator.createCropSprite(this.scene, 'crop_stage_1', 1); TextureGenerator.createCropSprite(this.scene, 'crop_stage_2', 2); TextureGenerator.createCropSprite(this.scene, 'crop_stage_3', 3); TextureGenerator.createCropSprite(this.scene, 'crop_stage_4', 4); - TextureGenerator.createCropSprite(this.scene, 'crop_stage_5', 5); + TextureGenerator.createCornSprites(this.scene); // Added Corn TextureGenerator.createGravestoneSprite(this.scene); TextureGenerator.createToolSprites(this.scene);