farma updejt
This commit is contained in:
53
DESIGN_DOC.md
Normal file
53
DESIGN_DOC.md
Normal file
@@ -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.*
|
||||||
46
OPEN_WORLD_PLAN.md
Normal file
46
OPEN_WORLD_PLAN.md
Normal file
@@ -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<string, ChunkData>`.
|
||||||
|
- `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.
|
||||||
19
TASKS.md
19
TASKS.md
@@ -147,6 +147,21 @@ Fokus na igralnost, loot in napredovanje.
|
|||||||
- [ ] Roads connecting Farm and City
|
- [ ] Roads connecting Farm and City
|
||||||
- [ ] Signposts
|
- [ ] 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** 🚧
|
**PROJECT STATUS: TRANSFORMING...** 🦋
|
||||||
*Last Updated: 2025-12-07 (Pathfinding & Assets Update)*
|
*Last Updated: 2025-12-08 (Antigravity Engine Integration)*
|
||||||
|
|||||||
@@ -131,9 +131,10 @@ class GameScene extends Phaser.Scene {
|
|||||||
this.player = new Player(this, 50, 50, this.terrainOffsetX, this.terrainOffsetY);
|
this.player = new Player(this, 50, 50, this.terrainOffsetX, this.terrainOffsetY);
|
||||||
|
|
||||||
// Dodaj 3 NPCje (Mixed)
|
// Dodaj 3 NPCje (Mixed)
|
||||||
|
// Dodaj 3 NPCje (Mixed) - Removed zombie
|
||||||
console.log('🧟 Initializing NPCs...');
|
console.log('🧟 Initializing NPCs...');
|
||||||
const npcTypes = ['zombie', 'villager', 'merchant'];
|
const npcTypes = ['villager', 'merchant'];
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < npcTypes.length; i++) {
|
||||||
const randomX = Phaser.Math.Between(40, 60); // Closer to center
|
const randomX = Phaser.Math.Between(40, 60); // Closer to center
|
||||||
const randomY = Phaser.Math.Between(40, 60);
|
const randomY = Phaser.Math.Between(40, 60);
|
||||||
console.log(`👤 Spawning NPC type: ${npcTypes[i]} at (${randomX}, ${randomY})`);
|
console.log(`👤 Spawning NPC type: ${npcTypes[i]} at (${randomX}, ${randomY})`);
|
||||||
@@ -141,13 +142,15 @@ class GameScene extends Phaser.Scene {
|
|||||||
this.npcs.push(npc);
|
this.npcs.push(npc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dodaj 10 dodatnih Zombijev!
|
// Dodaj 10 dodatnih Zombijev! - REMOVED BY REQUEST
|
||||||
|
/*
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const randomX = Phaser.Math.Between(10, 90);
|
const randomX = Phaser.Math.Between(10, 90);
|
||||||
const randomY = Phaser.Math.Between(10, 90);
|
const randomY = Phaser.Math.Between(10, 90);
|
||||||
const zombie = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, 'zombie');
|
const zombie = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, 'zombie');
|
||||||
this.npcs.push(zombie);
|
this.npcs.push(zombie);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// ELITE ZOMBIES v City območju (65,65 ± 15)
|
// ELITE ZOMBIES v City območju (65,65 ± 15)
|
||||||
console.log('👹 Spawning ELITE ZOMBIES in City...');
|
console.log('👹 Spawning ELITE ZOMBIES in City...');
|
||||||
@@ -223,6 +226,9 @@ class GameScene extends Phaser.Scene {
|
|||||||
.setScrollFactor(0).setDepth(10000);
|
.setScrollFactor(0).setDepth(10000);
|
||||||
|
|
||||||
console.log('✅ GameScene ready - FAZA 20 (Full Features)!');
|
console.log('✅ GameScene ready - FAZA 20 (Full Features)!');
|
||||||
|
|
||||||
|
// Start Engine
|
||||||
|
this.Antigravity_Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupCamera() {
|
setupCamera() {
|
||||||
@@ -512,6 +518,20 @@ class GameScene extends Phaser.Scene {
|
|||||||
// ANTIGRAVITY ENGINE UPDATE
|
// 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) {
|
Antigravity_Update(delta) {
|
||||||
// Globalni update klic
|
// Globalni update klic
|
||||||
if (window.Antigravity) {
|
if (window.Antigravity) {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class UIScene extends Phaser.Scene {
|
|||||||
this.overlayGraphics = this.add.graphics();
|
this.overlayGraphics = this.add.graphics();
|
||||||
this.overlayGraphics.setDepth(-1000); // Behind UI elements
|
this.overlayGraphics.setDepth(-1000); // Behind UI elements
|
||||||
|
|
||||||
this.createStatusBars();
|
this.drawUI();
|
||||||
|
|
||||||
this.createInventoryBar();
|
this.createInventoryBar();
|
||||||
this.createGoldDisplay();
|
this.createGoldDisplay();
|
||||||
this.createVirtualJoystick();
|
this.createVirtualJoystick();
|
||||||
@@ -176,36 +177,37 @@ class UIScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createStatusBars() {
|
drawUI() {
|
||||||
const x = 20;
|
const x = 20;
|
||||||
const y = 20;
|
const y = 20;
|
||||||
const width = 200;
|
const width = 150; // Zmanjšana širina (300 -> 150)
|
||||||
const height = 20;
|
const height = 15; // Zmanjšana višina (30 -> 15)
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
|
|
||||||
// Style
|
|
||||||
const boxStyle = {
|
|
||||||
fillStyle: { color: 0x000000, alpha: 0.5 },
|
|
||||||
lineStyle: { width: 2, color: 0xffffff, alpha: 0.8 }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. Health Bar
|
// 1. Health Bar
|
||||||
this.add.text(x, y - 5, 'HP', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' });
|
this.healthBar = this.drawBar(x, y, width, height, 0xff0000, 100, 'HP');
|
||||||
this.healthBar = this.createBar(x + 30, y, width, height, 0xff0000);
|
|
||||||
this.setBarValue(this.healthBar, 100);
|
|
||||||
|
|
||||||
// 2. Hunger Bar
|
// 2. Hunger Bar
|
||||||
this.add.text(x, y + height + padding - 5, 'HUN', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' });
|
this.hungerBar = this.drawBar(x, y + height + padding, width, height, 0xff8800, 80, 'HUN');
|
||||||
this.hungerBar = this.createBar(x + 30, y + height + padding, width, height, 0xff8800);
|
|
||||||
this.setBarValue(this.hungerBar, 80);
|
|
||||||
|
|
||||||
// 3. Thirst Bar
|
// 3. Thirst Bar
|
||||||
this.add.text(x, y + (height + padding) * 2 - 5, 'H2O', { fontSize: '12px', fontFamily: 'Courier New', fill: '#ffffff' });
|
this.thirstBar = this.drawBar(x, y + (height + padding) * 2, width, height, 0x0088ff, 90, 'H2O');
|
||||||
this.thirstBar = this.createBar(x + 30, y + (height + padding) * 2, width, height, 0x0088ff);
|
|
||||||
this.setBarValue(this.thirstBar, 90);
|
// 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'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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' });
|
||||||
}
|
}
|
||||||
|
|
||||||
createBar(x, y, width, height, color) {
|
|
||||||
// Background
|
// Background
|
||||||
const bg = this.add.graphics();
|
const bg = this.add.graphics();
|
||||||
bg.fillStyle(0x000000, 0.5);
|
bg.fillStyle(0x000000, 0.5);
|
||||||
@@ -215,8 +217,11 @@ class UIScene extends Phaser.Scene {
|
|||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
const fill = this.add.graphics();
|
const fill = this.add.graphics();
|
||||||
|
// Initial draw
|
||||||
fill.fillStyle(color, 1);
|
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 };
|
return { bg, fill, x, y, width, height, color };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* Glavni update loop za Engine
|
||||||
* @param {Phaser.Scene} scene
|
* @param {Phaser.Scene} scene
|
||||||
|
|||||||
@@ -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 {
|
class FarmingSystem {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.growthTimer = 0;
|
this.growthTimer = 0;
|
||||||
this.growthTickRate = 5000; // Check growth every 5 seconds (real time)
|
this.growthTickRate = 1000;
|
||||||
// Or better: based on TimeSystem days?
|
|
||||||
// For fast testing: rapid growth.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by InteractionSystem
|
// Called by InteractionSystem
|
||||||
@@ -14,40 +34,45 @@ class FarmingSystem {
|
|||||||
|
|
||||||
if (!tile) return false;
|
if (!tile) return false;
|
||||||
|
|
||||||
// 1. HARVEST (Right click or just click ripe crop?)
|
// 1. HARVEST
|
||||||
// Let's say if it has crop and it is ripe, harvest it regardless of tool.
|
|
||||||
if (tile.hasCrop) {
|
if (tile.hasCrop) {
|
||||||
const crop = terrain.cropsMap.get(`${gridX},${gridY}`);
|
const crop = terrain.cropsMap.get(`${gridX},${gridY}`);
|
||||||
console.log('🌾 Check harvest:', crop);
|
if (crop && crop.stage === CROP_DATA[crop.type].stages) {
|
||||||
if (crop && crop.stage === 4) {
|
|
||||||
this.harvest(gridX, gridY);
|
this.harvest(gridX, gridY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. TILLING (Requires Hoe)
|
// 2. TILLING
|
||||||
if (toolType === 'hoe') {
|
if (toolType === 'hoe') {
|
||||||
const typeName = tile.type.name || tile.type;
|
const typeName = tile.type.name || tile.type;
|
||||||
if (typeName.includes('grass') || typeName === 'dirt') {
|
if (typeName.includes('grass') || typeName === 'dirt') {
|
||||||
if (!tile.hasDecoration && !tile.hasCrop) {
|
if (!tile.hasDecoration && !tile.hasCrop) {
|
||||||
console.log('🚜 Tilling soil...');
|
terrain.setTileType(gridX, gridY, 'farmland');
|
||||||
terrain.setTileType(gridX, gridY, 'farmland'); // This sets it to string 'farmland' usually? or object? Assuming method handles it.
|
if (this.scene.soundManager) this.scene.soundManager.playDig();
|
||||||
// Play sound
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. PLANTING (Requires Seeds)
|
// 3. PLANTING
|
||||||
if (toolType === 'seeds') {
|
// Check if tool is a seed
|
||||||
|
const isSeed = toolType.startsWith('seeds_') || toolType === 'seeds';
|
||||||
|
|
||||||
|
if (isSeed) {
|
||||||
const typeName = tile.type.name || tile.type;
|
const typeName = tile.type.name || tile.type;
|
||||||
if (typeName === 'farmland' && !tile.hasCrop && !tile.hasDecoration) {
|
if (typeName === 'farmland' && !tile.hasCrop && !tile.hasDecoration) {
|
||||||
console.log('🌱 Planting seeds...');
|
// Determine crop type from seed name
|
||||||
this.plant(gridX, gridY);
|
// "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) {
|
if (this.scene.inventorySystem) {
|
||||||
this.scene.inventorySystem.removeItem('seeds', 1);
|
this.scene.inventorySystem.removeItem(toolType, 1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -56,57 +81,72 @@ class FarmingSystem {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
plant(x, y) {
|
plant(x, y, type = 'wheat') {
|
||||||
const terrain = this.scene.terrainSystem;
|
const terrain = this.scene.terrainSystem;
|
||||||
|
const data = CROP_DATA[type];
|
||||||
|
|
||||||
const cropData = {
|
const cropData = {
|
||||||
gridX: x,
|
gridX: x,
|
||||||
gridY: y,
|
gridY: y,
|
||||||
stage: 1, // Seeds
|
stage: 1,
|
||||||
type: 'wheat', // Default for now
|
type: type,
|
||||||
timer: 0,
|
timer: 0,
|
||||||
maxTime: 10 // Seconds per stage?
|
maxTime: data.growthTime
|
||||||
};
|
};
|
||||||
terrain.addCrop(x, y, cropData);
|
terrain.addCrop(x, y, cropData);
|
||||||
|
|
||||||
// Plant Sound
|
|
||||||
if (this.scene.soundManager) {
|
if (this.scene.soundManager) {
|
||||||
this.scene.soundManager.playPlant();
|
this.scene.soundManager.playPlant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quest Tracking
|
|
||||||
if (this.scene.questSystem) this.scene.questSystem.trackAction('plant');
|
if (this.scene.questSystem) this.scene.questSystem.trackAction('plant');
|
||||||
|
console.log(`🌱 Planted ${type} at ${x},${y}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
harvest(x, y) {
|
harvest(x, y) {
|
||||||
const terrain = this.scene.terrainSystem;
|
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) {
|
if (this.scene.soundManager) {
|
||||||
this.scene.soundManager.playHarvest();
|
this.scene.soundManager.playHarvest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn loot
|
// Loot
|
||||||
if (this.scene.interactionSystem) {
|
if (this.scene.interactionSystem) {
|
||||||
this.scene.interactionSystem.spawnLoot(x, y, 'wheat');
|
this.scene.interactionSystem.spawnLoot(x, y, data.harvestItem, 1);
|
||||||
this.scene.interactionSystem.spawnLoot(x, y, 'seeds'); // Return seeds
|
|
||||||
// 50% chance for extra seeds
|
// Chance for seeds
|
||||||
if (Math.random() > 0.5) this.scene.interactionSystem.spawnLoot(x, y, 'seeds');
|
if (Math.random() > 0.4) {
|
||||||
|
this.scene.interactionSystem.spawnLoot(x, y, data.seedItem, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove crop
|
// Extra harvest chance
|
||||||
terrain.removeCrop(x, y);
|
if (Math.random() > 0.5) {
|
||||||
|
this.scene.interactionSystem.spawnLoot(x, y, data.harvestItem, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Revert to dirt? Or keep farmland? Usually keeps farmland.
|
// XP Gain
|
||||||
|
if (this.scene.statsSystem) {
|
||||||
|
this.scene.statsSystem.addXP(data.harvestItem === 'corn' ? 15 : 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
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;
|
this.growthTimer += delta;
|
||||||
if (this.growthTimer < 1000) return;
|
if (this.growthTimer < 1000) return;
|
||||||
const secondsPassed = this.growthTimer / 1000;
|
const secondsPassed = this.growthTimer / 1000;
|
||||||
@@ -116,20 +156,15 @@ class FarmingSystem {
|
|||||||
if (!terrain) return;
|
if (!terrain) return;
|
||||||
|
|
||||||
for (const [key, crop] of terrain.cropsMap) {
|
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;
|
crop.timer += secondsPassed;
|
||||||
|
if (crop.timer >= data.growthTime) {
|
||||||
// Growth thresholds (fast for testing)
|
|
||||||
// Stage 1 -> 2: 5s
|
|
||||||
// Stage 2 -> 3: 10s
|
|
||||||
// Stage 3 -> 4: 15s
|
|
||||||
const needed = 5;
|
|
||||||
|
|
||||||
if (crop.timer >= needed) {
|
|
||||||
crop.stage++;
|
crop.stage++;
|
||||||
crop.timer = 0;
|
crop.timer = 0;
|
||||||
terrain.updateCropVisual(crop.gridX, crop.gridY, crop.stage);
|
terrain.updateCropVisual(crop.gridX, crop.gridY, crop.stage);
|
||||||
// Particle effect?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ class InteractionSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decor.hp <= 0) {
|
if (decor.hp <= 0) {
|
||||||
|
// XP REWARD (Requested by Logic)
|
||||||
|
if (this.scene.statsSystem) {
|
||||||
|
this.scene.statsSystem.addXP(5);
|
||||||
|
}
|
||||||
|
|
||||||
const prevType = decor.type;
|
const prevType = decor.type;
|
||||||
|
|
||||||
// Logic: If Tree (and not sapling), turn into Sapling immediately (Regrowth)
|
// Logic: If Tree (and not sapling), turn into Sapling immediately (Regrowth)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class SoundManager {
|
|||||||
this.beepHarvest();
|
this.beepHarvest();
|
||||||
} else if (key === 'build') {
|
} else if (key === 'build') {
|
||||||
this.beepBuild();
|
this.beepBuild();
|
||||||
|
} else if (key === 'dig') {
|
||||||
|
this.beepDig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,10 +299,28 @@ class SoundManager {
|
|||||||
playHarvest() { this.playSFX('harvest'); }
|
playHarvest() { this.playSFX('harvest'); }
|
||||||
playBuild() { this.playSFX('build'); }
|
playBuild() { this.playSFX('build'); }
|
||||||
playPickup() { this.playSFX('pickup'); }
|
playPickup() { this.playSFX('pickup'); }
|
||||||
|
playDig() { this.playSFX('dig'); } // Dodano
|
||||||
playAttack() { this.beepAttack(); }
|
playAttack() { this.beepAttack(); }
|
||||||
playHit() { this.beepHit(); }
|
playHit() { this.beepHit(); }
|
||||||
playFootstep() { this.beepFootstep(); }
|
playFootstep() { this.beepFootstep(); }
|
||||||
playDeath() { this.beepDeath(); }
|
playDeath() { this.beepDeath(); }
|
||||||
playRainSound() { this.startRainNoise(); }
|
playRainSound() { this.startRainNoise(); }
|
||||||
stopRainSound() { this.stopRainNoise(); }
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
class StatsSystem {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
|
|
||||||
|
// Leveling System
|
||||||
|
this.currentLevel = 1;
|
||||||
|
this.currentXP = 0;
|
||||||
|
this.xpToNextLevel = XP_REQUIRED_BASE;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
this.health = 100;
|
this.health = 100;
|
||||||
this.maxHealth = 100;
|
this.maxHealth = 100;
|
||||||
@@ -130,6 +141,18 @@ class StatsSystem {
|
|||||||
if (uiScene.hungerBar) uiScene.setBarValue(uiScene.hungerBar, this.hunger);
|
if (uiScene.hungerBar) uiScene.setBarValue(uiScene.hungerBar, this.hunger);
|
||||||
if (uiScene.thirstBar) uiScene.setBarValue(uiScene.thirstBar, this.thirst);
|
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
|
// Friendship System
|
||||||
@@ -149,4 +172,49 @@ class StatsSystem {
|
|||||||
this.friendship[npcType] = amount;
|
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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,10 +279,10 @@ class TerrainSystem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Place Trees dynamically during generation
|
// Place Trees dynamically during generation
|
||||||
this.placeTree(x, y, terrainType.name);
|
// this.placeTree(x, y, terrainType.name);
|
||||||
|
|
||||||
// Place Rocks dynamically
|
// 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 sprite = this.cropPool.get();
|
||||||
const screenPos = this.iso.toScreen(x, y);
|
const screenPos = this.iso.toScreen(x, y);
|
||||||
sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset));
|
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);
|
sprite.setOrigin(0.5, 1);
|
||||||
// Layer Objects (da igralec hodi okoli njih)
|
// Layer Objects (da igralec hodi okoli njih)
|
||||||
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS));
|
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS));
|
||||||
|
|||||||
@@ -384,36 +384,138 @@ class TextureGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createToolSprites(scene) {
|
static createToolSprites(scene) {
|
||||||
// Placeholder tool generation
|
// --- REALISTIC TOOLS (Procedural Generation) ---
|
||||||
|
|
||||||
|
// 1. AXE (Sekira)
|
||||||
if (!scene.textures.exists('item_axe')) {
|
if (!scene.textures.exists('item_axe')) {
|
||||||
const c = scene.textures.createCanvas('item_axe', 32, 32);
|
const c = scene.textures.createCanvas('item_axe', 32, 32);
|
||||||
const x = c.getContext();
|
const ctx = c.getContext();
|
||||||
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18);
|
ctx.clearRect(0, 0, 32, 32);
|
||||||
x.fillStyle = 'gray'; x.fillRect(12, 10, 8, 6);
|
|
||||||
|
// 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();
|
c.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. PICKAXE (Kramp)
|
||||||
if (!scene.textures.exists('item_pickaxe')) {
|
if (!scene.textures.exists('item_pickaxe')) {
|
||||||
const c = scene.textures.createCanvas('item_pickaxe', 32, 32);
|
const c = scene.textures.createCanvas('item_pickaxe', 32, 32);
|
||||||
const x = c.getContext();
|
const ctx = c.getContext();
|
||||||
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18);
|
ctx.clearRect(0, 0, 32, 32);
|
||||||
x.fillStyle = 'gray'; x.fillRect(8, 12, 16, 4);
|
|
||||||
|
// 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();
|
c.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. HOE (Motika)
|
||||||
if (!scene.textures.exists('item_hoe')) {
|
if (!scene.textures.exists('item_hoe')) {
|
||||||
const c = scene.textures.createCanvas('item_hoe', 32, 32);
|
const c = scene.textures.createCanvas('item_hoe', 32, 32);
|
||||||
const x = c.getContext();
|
const ctx = c.getContext();
|
||||||
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18); // Ročaj
|
ctx.clearRect(0, 0, 32, 32);
|
||||||
x.fillStyle = 'gray';
|
|
||||||
x.fillRect(10, 8, 12, 4); // Rezilo motike
|
// 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();
|
c.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. SWORD (Meč)
|
||||||
if (!scene.textures.exists('item_sword')) {
|
if (!scene.textures.exists('item_sword')) {
|
||||||
const c = scene.textures.createCanvas('item_sword', 32, 32);
|
const c = scene.textures.createCanvas('item_sword', 32, 32);
|
||||||
const x = c.getContext();
|
const ctx = c.getContext();
|
||||||
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 14); // Ročaj
|
ctx.clearRect(0, 0, 32, 32);
|
||||||
x.fillStyle = 'gray';
|
|
||||||
x.fillRect(12, 8, 8, 12); // Rezilo meča
|
// Handle
|
||||||
x.fillStyle = 'gold'; x.fillRect(14, 21, 4, 2); // Guard
|
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();
|
c.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,6 +527,8 @@ class TextureGenerator {
|
|||||||
{ name: 'stone', color: '#808080' }, // Siva
|
{ name: 'stone', color: '#808080' }, // Siva
|
||||||
{ name: 'seeds', color: '#90EE90' }, // Svetlo zelena
|
{ name: 'seeds', color: '#90EE90' }, // Svetlo zelena
|
||||||
{ name: 'wheat', color: '#FFD700' }, // Zlata
|
{ 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_bone', color: '#F5F5DC' }, // Beige
|
||||||
{ name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos)
|
{ name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos)
|
||||||
{ name: 'item_chip', color: '#00CED1' } // DarkTurquoise (elektronski chip)
|
{ name: 'item_chip', color: '#00CED1' } // DarkTurquoise (elektronski chip)
|
||||||
@@ -464,6 +568,41 @@ class TextureGenerator {
|
|||||||
canvas.refresh();
|
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
|
// Helper to generate ALL textures at once
|
||||||
generateAll() {
|
generateAll() {
|
||||||
TextureGenerator.createPlayerSprite(this.scene);
|
TextureGenerator.createPlayerSprite(this.scene);
|
||||||
@@ -473,15 +612,17 @@ class TextureGenerator {
|
|||||||
|
|
||||||
TextureGenerator.createFlowerSprite(this.scene);
|
TextureGenerator.createFlowerSprite(this.scene);
|
||||||
TextureGenerator.createBushSprite(this.scene);
|
TextureGenerator.createBushSprite(this.scene);
|
||||||
TextureGenerator.createSaplingSprite(this.scene, 'tree_sapling'); // Dodano
|
TextureGenerator.createSaplingSprite(this.scene, 'tree_sapling');
|
||||||
TextureGenerator.createTreeSprite(this.scene); // Volumetric
|
TextureGenerator.createTreeSprite(this.scene);
|
||||||
TextureGenerator.createRockSprite(this.scene); // Volumetric
|
TextureGenerator.createRockSprite(this.scene);
|
||||||
TextureGenerator.createCloudSprite(this.scene);
|
TextureGenerator.createCloudSprite(this.scene);
|
||||||
|
|
||||||
|
// Crops
|
||||||
TextureGenerator.createCropSprite(this.scene, 'crop_stage_1', 1);
|
TextureGenerator.createCropSprite(this.scene, 'crop_stage_1', 1);
|
||||||
TextureGenerator.createCropSprite(this.scene, 'crop_stage_2', 2);
|
TextureGenerator.createCropSprite(this.scene, 'crop_stage_2', 2);
|
||||||
TextureGenerator.createCropSprite(this.scene, 'crop_stage_3', 3);
|
TextureGenerator.createCropSprite(this.scene, 'crop_stage_3', 3);
|
||||||
TextureGenerator.createCropSprite(this.scene, 'crop_stage_4', 4);
|
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.createGravestoneSprite(this.scene);
|
||||||
TextureGenerator.createToolSprites(this.scene);
|
TextureGenerator.createToolSprites(this.scene);
|
||||||
|
|||||||
Reference in New Issue
Block a user