popravek zombijo
This commit is contained in:
112
TASKS.md
Normal file
112
TASKS.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# 🗺️ Task Map & Roadmap - NovaFarma
|
||||||
|
|
||||||
|
## 🟢 Phase 1: Core Systems (Foundation)
|
||||||
|
Vzpostavitev temeljev igre, sveta in igralca.
|
||||||
|
|
||||||
|
- [x] **Project Setup** (Vite, Phaser, Project Structure)
|
||||||
|
- [x] **Isometric Engine** (IsometricUtils, Grid conversion, Depth sorting)
|
||||||
|
- [x] **Terrain System**
|
||||||
|
- [x] Perlin Noise Generation (Grass, Dirt, Water)
|
||||||
|
- [x] Decoration Placement (Trees, Stones, Bushes)
|
||||||
|
- [x] Decoration Rendering (Correct depth, scale, variety)
|
||||||
|
- [x] **Player Entity**
|
||||||
|
- [x] Isometric Movement (WASD)
|
||||||
|
- [x] Animations (Idle, Walk)
|
||||||
|
- [x] Directional Facing (8-way or 4-way)
|
||||||
|
- [x] Tool Handling (Visual sword/axe/pickaxe in hand)
|
||||||
|
|
||||||
|
## 🟡 Phase 2: Gameplay Mechanics (Current Focus)
|
||||||
|
Dodajanje interakcije, boja in ekonomije.
|
||||||
|
|
||||||
|
- [x] **Interaction System**
|
||||||
|
- [x] Mouse Click Interaction (Precise grid selection)
|
||||||
|
- [x] Keyboard Interaction ('E' Key for ease of use)
|
||||||
|
- [x] Distance Checking (Range limits)
|
||||||
|
- [x] **NPC System (Zombies)**
|
||||||
|
- [x] Spawning & Rendering
|
||||||
|
- [x] Basic AI (Random Walk, Chase Player)
|
||||||
|
- [x] **Combat:**
|
||||||
|
- [x] Player Attack (Spacebar / Click with Weapon)
|
||||||
|
- [x] Zombie Health & Damage (Take Damage, Red Flash)
|
||||||
|
- [x] Death & Loot Drops (Skeleton/Bone drop)
|
||||||
|
- [x] **Taming:**
|
||||||
|
- [x] Interact to Tame (Empty hand / 'E')
|
||||||
|
- [x] Visuals (Hearts, Blue Eyes for tamed zombie)
|
||||||
|
- [x] Command Logic (Follow/Stay basics)
|
||||||
|
- [x] **Inventory & UI**
|
||||||
|
- [x] Inventory UI (Slots, Selection)
|
||||||
|
- [x] Crafting Menu ('C' Key)
|
||||||
|
- [x] Resource Gathering (Wood from trees, Stone from rocks)
|
||||||
|
- [x] **Save System**
|
||||||
|
- [x] LocalStorage Integration
|
||||||
|
- [x] Auto-load on start
|
||||||
|
- [x] Manual Save (F5)
|
||||||
|
|
||||||
|
## 🔴 Phase 3: Expansion (Next Steps)
|
||||||
|
Razširitev vsebine in izboljšava mehanik.
|
||||||
|
|
||||||
|
- [ ] **Farming Mechanics** (Polishing)
|
||||||
|
- [ ] Hoeing dirt to farmland
|
||||||
|
- [ ] Planting seeds
|
||||||
|
- [ ] Growth Stages (Time-based growth)
|
||||||
|
- [ ] Harvesting crops
|
||||||
|
- [ ] Watering mechanics
|
||||||
|
- [ ] **Advanced NPC AI**
|
||||||
|
- [ ] Pathfinding (A* or efficient grid traversal)
|
||||||
|
- [ ] Zombie Attacks Player (Player takes damage)
|
||||||
|
- [ ] Tamed Zombie Defense (Attacks enemies)
|
||||||
|
- [ ] Zombie Hordes (Night time events)
|
||||||
|
- [ ] **Economy**
|
||||||
|
- [ ] Merchant NPC (Trading interface)
|
||||||
|
- [ ] Selling crops/items for Gold
|
||||||
|
- [ ] Buying Seeds & Tools
|
||||||
|
- [ ] **Building System**
|
||||||
|
- [ ] Placing Walls/Fences (Snap to grid)
|
||||||
|
- [ ] Crafting Buildings (House, Barn)
|
||||||
|
- [ ] UI for selecting buildings
|
||||||
|
|
||||||
|
## 🔵 Phase 4: Polish & Visuals
|
||||||
|
Lepotni popravki in vzdušje.
|
||||||
|
|
||||||
|
- [ ] **Day/Night Cycle**
|
||||||
|
- [ ] Lighting overlay (Darkness at night)
|
||||||
|
- [ ] Dawn/Dusk transitions
|
||||||
|
- [ ] Night-only Zombie Spawns
|
||||||
|
- [ ] **Audio/SFX**
|
||||||
|
- [ ] Footsteps sounds
|
||||||
|
- [ ] Attack/Hit sounds
|
||||||
|
- [ ] Ambient nature sounds
|
||||||
|
- [ ] Background Music
|
||||||
|
- [ ] **Visual FX**
|
||||||
|
- [ ] Particle effects (Leaves falling, blood particles)
|
||||||
|
- [ ] UI Animations (Smooth inventory opening)
|
||||||
|
- [ ] Weather (Rain, Fog)
|
||||||
|
|
||||||
|
## 🟣 Phase 5: Story & Quests (Long Term)
|
||||||
|
Dodajanje globine in ciljev igri.
|
||||||
|
|
||||||
|
- [ ] **Story Mode**
|
||||||
|
- [ ] Intro Sequence
|
||||||
|
- [ ] Main Questline (Find the Cure / Rebuild the Town)
|
||||||
|
- [ ] **Boss Battles**
|
||||||
|
- [ ] "Zombie King" Boss
|
||||||
|
- [ ] Special Arenas
|
||||||
|
- [ ] **Quest System**
|
||||||
|
- [ ] NPC dialogue tasks ("Bring me 10 Wood")
|
||||||
|
- [ ] Rewards (Rare items, Gold)
|
||||||
|
|
||||||
|
## 🟠 Phase 6: Multiplayer & Export
|
||||||
|
Možnost igranja s prijatelji.
|
||||||
|
|
||||||
|
- [ ] **Local/LAN Multiplayer**
|
||||||
|
- [ ] Syncing Player Positions
|
||||||
|
- [ ] Syncing World State
|
||||||
|
- [ ] **Mobile Support**
|
||||||
|
- [ ] Touch Controls
|
||||||
|
- [ ] Responsive UI
|
||||||
|
- [ ] **Export**
|
||||||
|
- [ ] Desktop App (Electron)
|
||||||
|
- [ ] Android APK
|
||||||
|
|
||||||
|
---
|
||||||
|
*Last Updated: 2025-12-07*
|
||||||
@@ -1,71 +1,42 @@
|
|||||||
# Plan Optimizacij za NovaFarma
|
# 🛠️ Plan Optimizacij in Čiščenja - NovaFarma
|
||||||
|
|
||||||
Cilj: Izboljšati delovanje igre na počasnejših računalnikih (low-end devices) in zagotoviti stabilnih 60 FPS.
|
Datoteka namenjena tehničnim izboljšavam kode, refaktoringu in performančnim popravkom.
|
||||||
|
|
||||||
## 1. Rendering (Grafika)
|
## 🟢 1. Opravljene Optimizacije (Completed)
|
||||||
|
Stvari, ki so bile uspešno implementirane in izboljšale delovanje.
|
||||||
|
|
||||||
### A. Prehod na Phaser Tilemap ali Blitter (Visoka prioriteta)
|
- [x] **Distance Culling (Teren & Dekoracije)**
|
||||||
Trenutno `TerrainSystem` uporablja individualne `Phaser.GameObjects.Sprite` za vsako ploščico (tile) na tleh. Pri mapi 100x100 to pomeni 10.000 spritov, kar je veliko breme za CPU/GPU, tudi s cullingom.
|
- Sistem skriva ploščice (tiles) in drevesa, ki so daleč od igralca, da varčuje s CPU/GPU.
|
||||||
* **Rešitev:** Uporabiti `Phaser.Tilemaps` za izometrični pogled ali `Phaser.GameObjects.Blitter`. Blitter je izjemno hiter za renderiranje velikega števila enakih tekstur.
|
- [x] **Pooling Sistem**
|
||||||
* **Pričakovan prihranek:** Ogromen (CPU overhead za game objekte).
|
- `TerrainSystem` uporablja bazen spritov (`decPool`, `tilePool`) za ponovno uporabo objektov namesto nenehnega uničevanja in ustvarjanja.
|
||||||
s
|
- [x] **NPC Logic Throttling & Culling**
|
||||||
### B. Optimizacija Depth Sorting (Srednja prioriteta)
|
- NPC-ji daleč od igralca se ne posodabljajo in so skriti.
|
||||||
Trenutno se globina (z-index) računa in nastavlja za vsak `Sprite` (igralec, NPC, tiles, decorations) pogosto vsak frame.
|
- AI se ne izvaja vsak frame (uporaba timerjev za premik).
|
||||||
* **Rešitev:**
|
- [x] **Code Refactoring & Bug Fixes**
|
||||||
* Statične objekte (drevesa, kamni, tile) sortiraj samo enkrat ob generiranju ali ko pridejo v kader.
|
- [x] `InteractionSystem.js`: Centralizirana logika za klike in tipkovnico (E tipka). Odstranjeni odvečni listenerji.
|
||||||
* Dinamične objekte (igralec, NPC) sortiraj vsak frame, ampak samo glede na sosednje objekte.
|
- [x] `Player.js`: Urejena logika gibanja in napada (Spacebar).
|
||||||
* Uporabi "Display Layers" (Containerje) za ločevanje tal in objektov, da se tla nikoli ne sortirajo proti igralcu (tla so vedno spodaj).
|
- [x] `NPC.js`: Dodan Health Bar, Taming logika in Loot Drop.
|
||||||
|
- [x] `TextureGenerator`: Urejen draw items (Bone, Axe, Pickaxe).
|
||||||
|
|
||||||
### C. Zmanjšanje Draw Calls (Weather System)
|
## 🟡 2. Odprte Tehnične Naloge (To-Do)
|
||||||
Sistem vremena trenutno uporablja `Graphics` objekt, kjer riše vsako kapljo posebej s `strokePath` v JS zanki.
|
Stvari, ki bi jih bilo dobro urediti za boljšo stabilnost.
|
||||||
* **Rešitev:**
|
|
||||||
* Uporaba **Shaderjev (GLSL)** za dež in meglo. To prestavi delo na GPU in je praktično "zastonj" za CPU.
|
|
||||||
* Alternativa: Uporaba `Phaser.GameObjects.Particles` za dež.
|
|
||||||
|
|
||||||
## 2. Logika in Procesiranje
|
- [ ] **Global Error Handling**
|
||||||
|
- Ujeti napake, ki se zgodijo med igranjem (npr. manjkajoča metoda), in preprečiti sesutje igre (kot se je zgodilo z `handleDecorationClick`).
|
||||||
|
- [ ] **Centraliziran Loot Manager**
|
||||||
|
- Trenutno `InteractionSystem` upravlja z lootom. Bolje bi bilo imeti ločen `LootSystem` ali `ItemDropManager`, ki skrbi za fiziko dropov, pobiranje in despawn.
|
||||||
|
- [ ] **Z-Sorting (Depth) Optimizacija**
|
||||||
|
- Globina se še vedno nastavlja pogosto. Lahko bi optimizirali tako, da se statični objekti sortirajo samo enkrat.
|
||||||
|
|
||||||
### A. Izboljšan Culling (Visoka prioriteta)
|
## 🔴 3. Performančne Nadgradnje (High-End)
|
||||||
Trenutno `updateCulling` teče vsak frame (ali pogosto). Zanka gre čez vse tile (10.000) in preverja distance.
|
Če bo igra postala počasna pri velikem svetu (256x256).
|
||||||
* **Rešitev:**
|
|
||||||
* **Spatial Hashing / Grid Partitioning:** Razdeli svet na "Chunke" (npr. 10x10 tileov). Renderiraj samo chunke, ki se dotikajo kamere.
|
|
||||||
* Posodobi culling samo, ko se kamera premakne za določeno razdaljo (npr. 50px), ne vsak frame.
|
|
||||||
|
|
||||||
### B. Throttling AI in Posodobitev (Srednja prioriteta)
|
- [ ] **Phaser Blitter / Tilemap**
|
||||||
NPC-ji se posodabljajo vsak frame (60-krat na sekundo).
|
- Trenutno je svet sestavljen iz tisočev spritov. Prehod na `Phaser.Blitter` ali `Tilemap` bi drastično zmanjšal porabo RAM-a in CPU-ja.
|
||||||
* **Rešitev:**
|
- [ ] **Spatial Hashing za Kolizijo**
|
||||||
* Posodabljaj logiko NPC-jev (iskanje poti, odločanje) samo vsakih 10-20 framov ali 100ms.
|
- Namesto preverjanja razdalje do vsakega NPC-ja uporabiti prostorsko mrežo (Spatial Grid) za hitrejše iskanje sosedov.
|
||||||
* Animacije se še vedno vrtijo gladko, ampak "možgani" delajo počasneje.
|
- [ ] **Web Workers za AI**
|
||||||
* Oddaljeni NPC-ji (izven ekrana) se sploh ne posodabljajo ali pa zelo redko.
|
- Prestavi pathfinding (iskanje poti) na ločen thread (Worker), da ne blokira glavne igre.
|
||||||
|
|
||||||
### C. Fizika
|
---
|
||||||
Če uporabljamo Arcade Physics za vse kolizije, je lahko počasno.
|
*Status: Koda je trenutno stabilna in očiščena (7.12.2025).*
|
||||||
* **Rešitev:** Za statične objekte (drevesa, zidovi) uporabljaj preprosto preverjanje mreže (`grid[x][y].isSolid`), namesto da vsako drevo ustvari fizikalno telo.
|
|
||||||
|
|
||||||
## 3. Upravljanje Spomina (Memory)
|
|
||||||
|
|
||||||
### A. Object Pooling (Že delno implementirano)
|
|
||||||
Zagotoviti, da se za VSE pogoste objekte uporablja pool:
|
|
||||||
* Projektili (če bodo).
|
|
||||||
* Partikli.
|
|
||||||
* UI elementi (floating text damage).
|
|
||||||
* Inventory icons.
|
|
||||||
|
|
||||||
### B. Texture Management
|
|
||||||
* Zagotoviti, da so vse teksture v **Sprite Atlasu** (Texture Atlas) za zmanjšanje "Texture Swaps" na GPU.
|
|
||||||
* Unloadati asete, ki niso v uporabi (npr. med prehodi scen, čeprav za to igro verjetno ni kritično).
|
|
||||||
|
|
||||||
## 4. Settings (Nastavitve za uporabnika)
|
|
||||||
|
|
||||||
Dodati meni "Settings", kjer lahko uporabnik izbere:
|
|
||||||
* **View Distance:** Koliko tile-ov daleč se renderira.
|
|
||||||
* **Weather Effects:** On/Off/Simple (brez delcev).
|
|
||||||
* **Shadows:** On/Off.
|
|
||||||
* **Particles:** High/Low.
|
|
||||||
|
|
||||||
## Akcijski Plan (Koraki)
|
|
||||||
|
|
||||||
1. **Korak 1 (Takoj):** [DONE] Implementiraj "Throttling" za `updateCulling`.
|
|
||||||
2. **Korak 2:** [DONE] Prepiši `WeatherSystem` za uporabo Phaser Particles.
|
|
||||||
3. **Korak 3:** [DONE] Implementiraj "Throttling" in Distance Culling za NPC AI.
|
|
||||||
4. **Korak 4:** [DONE] Implementacija `Phaser.Blitter` za teren (Faza 4 fix).
|
|
||||||
5. **Korak 5:** [DONE] Povezava Settings z Weather in Terrain sistemi.
|
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ class NPC {
|
|||||||
if (this.attackCooldownTimer > 0) return;
|
if (this.attackCooldownTimer > 0) return;
|
||||||
this.attackCooldownTimer = 1500;
|
this.attackCooldownTimer = 1500;
|
||||||
|
|
||||||
|
// Attack Animation (Jump)
|
||||||
if (this.sprite) {
|
if (this.sprite) {
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this.sprite,
|
targets: this.sprite,
|
||||||
@@ -324,9 +325,12 @@ class NPC {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.scene.statsSystem) {
|
// Apply Damage to Player
|
||||||
|
if (this.scene.player && this.scene.player.takeDamage) {
|
||||||
|
console.log('🧟 Zombie ATTACKS Player!');
|
||||||
|
this.scene.player.takeDamage(10);
|
||||||
|
} else if (this.scene.statsSystem) {
|
||||||
this.scene.statsSystem.takeDamage(10);
|
this.scene.statsSystem.takeDamage(10);
|
||||||
this.scene.cameras.main.shake(100, 0.005);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,6 +445,52 @@ class NPC {
|
|||||||
if (idx > -1) this.scene.npcs.splice(idx, 1);
|
if (idx > -1) this.scene.npcs.splice(idx, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tame() {
|
||||||
|
if (this.state === 'TAMED' || this.type !== 'zombie') return;
|
||||||
|
|
||||||
|
this.state = 'TAMED';
|
||||||
|
console.log('🧟❤️ Zombie TAMED!');
|
||||||
|
|
||||||
|
// Visual Feedback
|
||||||
|
const headX = this.sprite.x;
|
||||||
|
const headY = this.sprite.y - 40;
|
||||||
|
|
||||||
|
const heart = this.scene.add.text(headX, headY, '❤️', { fontSize: '20px' });
|
||||||
|
heart.setOrigin(0.5);
|
||||||
|
heart.setDepth(this.sprite.depth + 100);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: heart,
|
||||||
|
y: headY - 30,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 1500,
|
||||||
|
onComplete: () => heart.destroy()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change color slightly to indicate friend
|
||||||
|
this.sprite.setTint(0xAAFFAA);
|
||||||
|
|
||||||
|
// Hide Health Bar if tamed (optional)
|
||||||
|
if (this.healthBarBg) {
|
||||||
|
this.healthBarBg.setVisible(false);
|
||||||
|
this.healthBar.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addTamedEyes();
|
||||||
|
}
|
||||||
|
|
||||||
|
addTamedEyes() {
|
||||||
|
if (this.eyesGroup) return;
|
||||||
|
|
||||||
|
this.eyesGroup = this.scene.add.container(this.sprite.x, this.sprite.y);
|
||||||
|
this.eyesGroup.setDepth(this.sprite.depth + 1);
|
||||||
|
|
||||||
|
const eyeL = this.scene.add.rectangle(-4, -54, 4, 4, 0x00FFFF); // Cyan eyes
|
||||||
|
const eyeR = this.scene.add.rectangle(4, -54, 4, 4, 0x00FFFF);
|
||||||
|
|
||||||
|
this.eyesGroup.add([eyeL, eyeR]);
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.sprite) this.sprite.destroy();
|
if (this.sprite) this.sprite.destroy();
|
||||||
if (this.healthBar) this.healthBar.destroy();
|
if (this.healthBar) this.healthBar.destroy();
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ class Player {
|
|||||||
this.direction = 'down';
|
this.direction = 'down';
|
||||||
this.lastDir = { x: 0, y: 1 }; // Default south
|
this.lastDir = { x: 0, y: 1 }; // Default south
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
this.hp = 100;
|
||||||
|
this.maxHp = 100;
|
||||||
|
this.isDead = false;
|
||||||
|
|
||||||
// Kreira sprite
|
// Kreira sprite
|
||||||
this.createSprite();
|
this.createSprite();
|
||||||
|
|
||||||
@@ -29,13 +34,52 @@ class Player {
|
|||||||
|
|
||||||
// Space za napad
|
// Space za napad
|
||||||
this.scene.input.keyboard.on('keydown-SPACE', () => {
|
this.scene.input.keyboard.on('keydown-SPACE', () => {
|
||||||
this.attack();
|
if (!this.isDead) this.attack();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Začetna pozicija
|
// Začetna pozicija
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
takeDamage(amount) {
|
||||||
|
if (this.isDead) return;
|
||||||
|
|
||||||
|
this.hp -= amount;
|
||||||
|
console.log(`Player HP: ${this.hp}`);
|
||||||
|
|
||||||
|
// Visual Feedback
|
||||||
|
this.scene.cameras.main.shake(100, 0.01);
|
||||||
|
this.sprite.setTint(0xff0000);
|
||||||
|
this.scene.time.delayedCall(200, () => {
|
||||||
|
if (!this.isDead) this.sprite.clearTint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update UI (če obstaja StatsSystem ali UI)
|
||||||
|
if (this.scene.statsSystem) {
|
||||||
|
// Scene specific stats update if linked
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hp <= 0) {
|
||||||
|
this.die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
this.isDead = true;
|
||||||
|
this.sprite.setTint(0x555555);
|
||||||
|
this.sprite.setRotation(Math.PI / 2); // Lie down
|
||||||
|
console.log('💀 PLAYER DIED');
|
||||||
|
|
||||||
|
// Show Game Over / Reload
|
||||||
|
const txt = this.scene.add.text(this.scene.cameras.main.midPoint.x, this.scene.cameras.main.midPoint.y, 'YOU DIED', {
|
||||||
|
fontSize: '64px', color: '#ff0000', fontStyle: 'bold', stroke: '#000', strokeThickness: 6
|
||||||
|
}).setOrigin(0.5).setDepth(20000);
|
||||||
|
|
||||||
|
this.scene.time.delayedCall(3000, () => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createSprite() {
|
createSprite() {
|
||||||
// Prefer animated sprite if available
|
// Prefer animated sprite if available
|
||||||
let texKey = 'player_walk'; // Spritesheet
|
let texKey = 'player_walk'; // Spritesheet
|
||||||
|
|||||||
40
src/game.js
40
src/game.js
@@ -1,3 +1,43 @@
|
|||||||
|
// --- Global Error Handler ---
|
||||||
|
class ErrorHandler {
|
||||||
|
static init() {
|
||||||
|
window.onerror = (message, source, lineno, colno, error) => {
|
||||||
|
ErrorHandler.showError(message, source, lineno, colno, error);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
ErrorHandler.showError('Unhandled Promise Rejection', '', 0, 0, event.reason);
|
||||||
|
});
|
||||||
|
console.log('🛡️ Global Error Handler Initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
static showError(message, source, lineno, colno, error) {
|
||||||
|
console.error('🔥 CRITICAL ERROR:', message);
|
||||||
|
if (document.getElementById('error-overlay')) return;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.id = 'error-overlay';
|
||||||
|
div.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(20,0,0,0.95);color:#ffaaaa;z-index:999999;display:flex;flex-direction:column;justify-content:center;align-items:center;font-family:monospace;padding:20px;text-align:center;';
|
||||||
|
|
||||||
|
const stack = error && error.stack ? error.stack : '';
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<h1 style="color:#ff4444;margin-bottom:20px;">☠️ OOPS! GAME CRASHED ☠️</h1>
|
||||||
|
<div style="background:rgba(0,0,0,0.5);padding:15px;border:1px solid #ff4444;max-width:800px;max-height:300px;overflow:auto;text-align:left;margin-bottom:20px;white-space:pre-wrap;">
|
||||||
|
<strong>${message}</strong><br>
|
||||||
|
<small>${source}:${lineno}:${colno}</small><br><br>
|
||||||
|
${stack}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onclick="window.location.reload()" style="padding:15px 30px;font-size:18px;font-weight:bold;background:#44aa44;color:white;border:none;cursor:pointer;border-radius:5px;margin-right:10px;">🔄 RELOAD GAME</button>
|
||||||
|
<button onclick="document.getElementById('error-overlay').remove()" style="padding:15px 30px;font-size:14px;background:#666;color:white;border:none;cursor:pointer;border-radius:5px;">IGNORE</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorHandler.init();
|
||||||
|
|
||||||
// Phaser Game Configuration
|
// Phaser Game Configuration
|
||||||
const config = {
|
const config = {
|
||||||
type: Phaser.CANVAS, // Canvas renderer za pixel-perfect ostrino
|
type: Phaser.CANVAS, // Canvas renderer za pixel-perfect ostrino
|
||||||
|
|||||||
@@ -448,8 +448,21 @@ class UIScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
// Here we could update bars based on player stats
|
if (!this.gameScene) return;
|
||||||
// if (this.gameScene && this.gameScene.player) { ... }
|
|
||||||
|
// Sync HP Bar
|
||||||
|
if (this.gameScene.player && this.healthBar) {
|
||||||
|
const hp = this.gameScene.player.hp !== undefined ? this.gameScene.player.hp : 100;
|
||||||
|
const maxHp = this.gameScene.player.maxHp || 100;
|
||||||
|
this.setBarValue(this.healthBar, (hp / maxHp) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync Hunger/Thirst (if stats system exists)
|
||||||
|
if (this.gameScene.statsSystem && this.hungerBar && this.thirstBar) {
|
||||||
|
const stats = this.gameScene.statsSystem;
|
||||||
|
this.setBarValue(this.hungerBar, stats.hunger);
|
||||||
|
this.setBarValue(this.thirstBar, stats.thirst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toggleBuildMenu(isVisible) {
|
toggleBuildMenu(isVisible) {
|
||||||
if (!this.buildMenuContainer) {
|
if (!this.buildMenuContainer) {
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ class TerrainSystem {
|
|||||||
this.lastCullX = -9999;
|
this.lastCullX = -9999;
|
||||||
this.lastCullY = -9999;
|
this.lastCullY = -9999;
|
||||||
|
|
||||||
// Object Pools
|
|
||||||
// Tiles will use Blitter, so no Sprite Pool needed for them.
|
|
||||||
this.blitters = new Map(); // Key: textureKey, Value: Blitter Object
|
|
||||||
// Object Pools
|
// Object Pools
|
||||||
this.tilePool = new ObjectPool(
|
this.tilePool = new ObjectPool(
|
||||||
() => {
|
() => {
|
||||||
@@ -104,6 +101,23 @@ class TerrainSystem {
|
|||||||
return baseType;
|
return baseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper za določanje tipa terena glede na noise vrednost
|
||||||
|
getTerrainType(value) {
|
||||||
|
if (value < this.terrainTypes.WATER.threshold) return this.terrainTypes.WATER;
|
||||||
|
if (value < this.terrainTypes.SAND.threshold) return this.terrainTypes.SAND;
|
||||||
|
if (value < this.terrainTypes.GRASS_FULL.threshold) return this.terrainTypes.GRASS_FULL; // Fallback grass
|
||||||
|
if (value < this.terrainTypes.GRASS_TOP.threshold) return this.terrainTypes.GRASS_TOP;
|
||||||
|
if (value < this.terrainTypes.DIRT.threshold) return this.terrainTypes.DIRT;
|
||||||
|
return this.terrainTypes.STONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTile(x, y) {
|
||||||
|
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
||||||
|
return this.tiles[y][x];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Generiraj teksture za tiles (da ne uporabljamo Počasnih Graphics objektov)
|
// Generiraj teksture za tiles (da ne uporabljamo Počasnih Graphics objektov)
|
||||||
createTileTextures() {
|
createTileTextures() {
|
||||||
console.log('🎨 Creating tile textures...');
|
console.log('🎨 Creating tile textures...');
|
||||||
@@ -178,45 +192,6 @@ class TerrainSystem {
|
|||||||
graphics.closePath();
|
graphics.closePath();
|
||||||
graphics.fillPath();
|
graphics.fillPath();
|
||||||
|
|
||||||
// 4. Add Minecraft-style texture pattern on top
|
|
||||||
if (type.name === 'grass_full' || type.name === 'grass_top') {
|
|
||||||
// Grass texture: Random pixel pattern
|
|
||||||
graphics.fillStyle(0x4a9d3f, 0.3); // Slightly darker green
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
const px = Math.random() * tileW;
|
|
||||||
const py = Math.random() * tileH;
|
|
||||||
graphics.fillRect(px, py, 2, 2);
|
|
||||||
}
|
|
||||||
} else if (type.name === 'dirt') {
|
|
||||||
// Dirt texture: Darker spots
|
|
||||||
graphics.fillStyle(0x6d5838, 0.4);
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
const px = Math.random() * tileW;
|
|
||||||
const py = Math.random() * tileH;
|
|
||||||
graphics.fillRect(px, py, 3, 3);
|
|
||||||
}
|
|
||||||
} else if (type.name === 'stone') {
|
|
||||||
// Stone texture: Gray spots
|
|
||||||
graphics.fillStyle(0x666666, 0.3);
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
const px = Math.random() * tileW;
|
|
||||||
const py = Math.random() * tileH;
|
|
||||||
graphics.fillRect(px, py, 2, 1);
|
|
||||||
}
|
|
||||||
} else if (type.name === 'path') {
|
|
||||||
// Path texture: Small gravel stones
|
|
||||||
graphics.fillStyle(0x7a5e42, 0.5); // Darker brown
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
const px = Math.random() * tileW;
|
|
||||||
const py = Math.random() * tileH;
|
|
||||||
graphics.fillRect(px, py, 2, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Crisp black outline for block definition
|
|
||||||
graphics.lineStyle(1, 0x000000, 0.3);
|
|
||||||
graphics.strokePath();
|
|
||||||
|
|
||||||
// Generate texture
|
// Generate texture
|
||||||
graphics.generateTexture(key, tileW, tileH + thickness);
|
graphics.generateTexture(key, tileW, tileH + thickness);
|
||||||
graphics.destroy();
|
graphics.destroy();
|
||||||
@@ -227,27 +202,21 @@ class TerrainSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createGravestoneSprite() {
|
createGravestoneSprite() {
|
||||||
// Extract gravestone from objects_pack (approx position in atlas)
|
|
||||||
// Gravestone appears to be around position row 4, column 4-5 in the pack
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const size = 32; // Approximate sprite size
|
const size = 32;
|
||||||
canvas.width = size;
|
canvas.width = size;
|
||||||
canvas.height = size;
|
canvas.height = size;
|
||||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||||
|
|
||||||
const sourceTexture = this.scene.textures.get('objects_pack');
|
if (this.scene.textures.exists('objects_pack')) {
|
||||||
const sourceImg = sourceTexture.getSourceImage();
|
const sourceTexture = this.scene.textures.get('objects_pack');
|
||||||
|
const sourceImg = sourceTexture.getSourceImage();
|
||||||
// Extract gravestone (cross tombstone) - estimated coords
|
const sourceX = 240;
|
||||||
// Adjust these values based on actual sprite sheet layout
|
const sourceY = 160;
|
||||||
const sourceX = 240; // Approximate X position
|
ctx.drawImage(sourceImg, sourceX, sourceY, size, size, 0, 0, size, size);
|
||||||
const sourceY = 160; // Approximate Y position
|
this.scene.textures.addCanvas('gravestone', canvas);
|
||||||
|
console.log('✅ Gravestone sprite extracted!');
|
||||||
ctx.drawImage(sourceImg, sourceX, sourceY, size, size, 0, 0, size, size);
|
}
|
||||||
|
|
||||||
// Create texture
|
|
||||||
this.scene.textures.addCanvas('gravestone', canvas);
|
|
||||||
console.log('✅ Gravestone sprite extracted!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generiraj teren (data only)
|
// Generiraj teren (data only)
|
||||||
@@ -257,90 +226,51 @@ class TerrainSystem {
|
|||||||
// Zagotovi teksture
|
// Zagotovi teksture
|
||||||
this.createTileTextures();
|
this.createTileTextures();
|
||||||
|
|
||||||
// DELETED Blitter Init
|
if (!this.scene.textures.exists('flower')) TextureGenerator.createFlowerSprite(this.scene, 'flower');
|
||||||
|
|
||||||
// Zagotovi decoration teksture - check for custom sprites first
|
|
||||||
if (!this.scene.textures.exists('flower')) {
|
|
||||||
TextureGenerator.createFlowerSprite(this.scene, 'flower');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bush - use custom stone sprite if available
|
|
||||||
if (this.scene.textures.exists('stone_sprite')) {
|
if (this.scene.textures.exists('stone_sprite')) {
|
||||||
// Use stone_sprite for bushes (rocks)
|
if (!this.scene.textures.exists('bush')) this.scene.textures.addImage('bush', this.scene.textures.get('stone_sprite').getSourceImage());
|
||||||
if (!this.scene.textures.exists('bush')) {
|
|
||||||
this.scene.textures.addImage('bush', this.scene.textures.get('stone_sprite').getSourceImage());
|
|
||||||
}
|
|
||||||
} else if (!this.scene.textures.exists('bush')) {
|
} else if (!this.scene.textures.exists('bush')) {
|
||||||
TextureGenerator.createBushSprite(this.scene, 'bush');
|
TextureGenerator.createBushSprite(this.scene, 'bush');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tree - use custom tree sprite if available
|
|
||||||
if (this.scene.textures.exists('tree_sprite')) {
|
if (this.scene.textures.exists('tree_sprite')) {
|
||||||
if (!this.scene.textures.exists('tree')) {
|
if (!this.scene.textures.exists('tree')) this.scene.textures.addImage('tree', this.scene.textures.get('tree_sprite').getSourceImage());
|
||||||
this.scene.textures.addImage('tree', this.scene.textures.get('tree_sprite').getSourceImage());
|
|
||||||
}
|
|
||||||
} else if (!this.scene.textures.exists('tree')) {
|
} else if (!this.scene.textures.exists('tree')) {
|
||||||
TextureGenerator.createTreeSprite(this.scene, 'tree');
|
TextureGenerator.createTreeSprite(this.scene, 'tree');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gravestone - extract from objects_pack
|
|
||||||
if (this.scene.textures.exists('objects_pack') && !this.scene.textures.exists('gravestone')) {
|
if (this.scene.textures.exists('objects_pack') && !this.scene.textures.exists('gravestone')) {
|
||||||
this.createGravestoneSprite();
|
this.createGravestoneSprite();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crop textures
|
|
||||||
for (let i = 1; i <= 4; i++) {
|
for (let i = 1; i <= 4; i++) {
|
||||||
if (!this.scene.textures.exists(`crop_stage_${i}`))
|
if (!this.scene.textures.exists(`crop_stage_${i}`)) TextureGenerator.createCropSprite(this.scene, `crop_stage_${i}`, i);
|
||||||
TextureGenerator.createCropSprite(this.scene, `crop_stage_${i}`, i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.decorationsMap.clear();
|
this.decorationsMap.clear();
|
||||||
this.decorations = [];
|
this.decorations = [];
|
||||||
this.cropsMap.clear();
|
this.cropsMap.clear();
|
||||||
|
|
||||||
// Generiraj tile podatke
|
|
||||||
for (let y = 0; y < this.height; y++) {
|
for (let y = 0; y < this.height; y++) {
|
||||||
this.tiles[y] = [];
|
this.tiles[y] = [];
|
||||||
for (let x = 0; x < this.width; x++) {
|
for (let x = 0; x < this.width; x++) {
|
||||||
const noiseValue = this.noise.getNormalized(x, y, 0.05, 4);
|
const noiseValue = this.noise.getNormalized(x, y, 0.05, 4);
|
||||||
|
|
||||||
// Elevation (druga Perlin noise layer za hribe)
|
|
||||||
let elevation = this.noise.getNormalized(x, y, 0.03, 3);
|
let elevation = this.noise.getNormalized(x, y, 0.03, 3);
|
||||||
|
|
||||||
// Get terrain type based on BOTH noise and elevation (Y-layer)
|
|
||||||
let terrainType = this.getTerrainTypeByElevation(noiseValue, elevation);
|
let terrainType = this.getTerrainTypeByElevation(noiseValue, elevation);
|
||||||
|
|
||||||
// === PATH GENERATION ===
|
|
||||||
// Use a separate noise layer for paths (higher frequency for winding roads)
|
|
||||||
const pathNoise = this.noise.getNormalized(x, y, 0.08, 2);
|
const pathNoise = this.noise.getNormalized(x, y, 0.08, 2);
|
||||||
|
|
||||||
// Create minimal paths - if noise is in a very specific narrow band
|
|
||||||
// Avoid water (0.3 threshold) and edges
|
|
||||||
if (pathNoise > 0.48 && pathNoise < 0.52 && noiseValue > 0.35) {
|
if (pathNoise > 0.48 && pathNoise < 0.52 && noiseValue > 0.35) {
|
||||||
terrainType = this.terrainTypes.PATH;
|
terrainType = this.terrainTypes.PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === FLOATING ISLAND EDGE ===
|
const edgeDistance = 2;
|
||||||
const edgeDistance = 2; // Tiles from edge (tighter border)
|
const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance || y < edgeDistance || y >= this.height - edgeDistance;
|
||||||
const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance ||
|
const isEdge = x === 0 || x === this.width - 1 || y === 0 || y === this.height - 1;
|
||||||
y < edgeDistance || y >= this.height - edgeDistance;
|
|
||||||
|
|
||||||
const isEdge = x === 0 || x === this.width - 1 ||
|
if (isEdge) terrainType = this.terrainTypes.STONE;
|
||||||
y === 0 || y === this.height - 1;
|
|
||||||
|
|
||||||
// Override terrain type at edges
|
if (isEdge) elevation = 0;
|
||||||
if (isEdge) {
|
else if (isNearEdge) elevation = Math.max(0, elevation - 0.2);
|
||||||
terrainType = this.terrainTypes.STONE; // Cliff wall (stone edge)
|
|
||||||
} else if (isNearEdge) {
|
|
||||||
// Keep Y-layer system active
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flatten edges (cliff drop-off for floating island effect)
|
|
||||||
if (isEdge) {
|
|
||||||
elevation = 0; // Flat cliff wall
|
|
||||||
} else if (isNearEdge) {
|
|
||||||
elevation = Math.max(0, elevation - 0.2); // Slight dip near edge
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tiles[y][x] = {
|
this.tiles[y][x] = {
|
||||||
gridX: x,
|
gridX: x,
|
||||||
@@ -348,45 +278,32 @@ class TerrainSystem {
|
|||||||
type: terrainType.name,
|
type: terrainType.name,
|
||||||
texture: terrainType.texture,
|
texture: terrainType.texture,
|
||||||
height: noiseValue,
|
height: noiseValue,
|
||||||
elevation: elevation, // 0-1 (0=low, 1=high)
|
elevation: elevation,
|
||||||
yLayer: terrainType.yLayer, // Y-stacking layer
|
yLayer: terrainType.yLayer,
|
||||||
hasDecoration: false,
|
hasDecoration: false,
|
||||||
hasCrop: false
|
hasCrop: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generacija dekoracij (shranimo v data, ne ustvarjamo sprite-ov še)
|
|
||||||
if (x > 5 && x < this.width - 5 && y > 5 && y < this.height - 5) {
|
if (x > 5 && x < this.width - 5 && y > 5 && y < this.height - 5) {
|
||||||
let decorType = null;
|
let decorType = null;
|
||||||
let maxHp = 1;
|
let maxHp = 1;
|
||||||
let scale = 1.0; // Default scale
|
let scale = 1.0;
|
||||||
|
|
||||||
if (terrainType.name.includes('grass')) { // Check ANY grass type
|
if (terrainType.name.includes('grass')) {
|
||||||
const rand = Math.random();
|
const rand = Math.random();
|
||||||
|
|
||||||
// Na hribih več kamnov
|
|
||||||
if (elevation > 0.6 && rand < 0.05) {
|
if (elevation > 0.6 && rand < 0.05) {
|
||||||
decorType = 'bush'; // Kamni
|
decorType = 'bush';
|
||||||
maxHp = 5;
|
maxHp = 5;
|
||||||
} else if (rand < 0.025) { // Reduced to 2.5% trees (optimum balance)
|
} else if (rand < 0.025) {
|
||||||
decorType = 'tree';
|
decorType = 'tree';
|
||||||
maxHp = 5;
|
maxHp = 5;
|
||||||
|
|
||||||
// TREE SIZING LOGIC
|
|
||||||
// 20% Normal (1.0)
|
|
||||||
// 60% Medium (0.6-0.8)
|
|
||||||
// 20% Tiny (0.2)
|
|
||||||
const sizeRand = Math.random();
|
const sizeRand = Math.random();
|
||||||
if (sizeRand < 0.2) {
|
if (sizeRand < 0.2) scale = 0.25;
|
||||||
scale = 0.25; // 20% Tiny (0.25 visible enough)
|
else if (sizeRand < 0.8) scale = 0.6 + Math.random() * 0.2;
|
||||||
} else if (sizeRand < 0.8) {
|
else scale = 1.0;
|
||||||
scale = 0.6 + Math.random() * 0.2; // 60% Scale 0.6-0.8
|
|
||||||
} else {
|
|
||||||
scale = 1.0; // 20% Full size
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (rand < 0.03) {
|
} else if (rand < 0.03) {
|
||||||
decorType = 'gravestone'; // 💀 Nagrobniki
|
decorType = 'gravestone';
|
||||||
maxHp = 10; // Težje uničiti
|
maxHp = 10;
|
||||||
} else if (rand < 0.08) {
|
} else if (rand < 0.08) {
|
||||||
decorType = 'flower';
|
decorType = 'flower';
|
||||||
maxHp = 1;
|
maxHp = 1;
|
||||||
@@ -405,37 +322,29 @@ class TerrainSystem {
|
|||||||
id: key,
|
id: key,
|
||||||
maxHp: maxHp,
|
maxHp: maxHp,
|
||||||
hp: maxHp,
|
hp: maxHp,
|
||||||
scale: scale // Save scale
|
scale: scale
|
||||||
};
|
};
|
||||||
|
|
||||||
this.decorations.push(decorData);
|
this.decorations.push(decorData);
|
||||||
this.decorationsMap.set(key, decorData);
|
this.decorationsMap.set(key, decorData);
|
||||||
|
|
||||||
this.tiles[y][x].hasDecoration = true;
|
this.tiles[y][x].hasDecoration = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Terrain and decorations data generated!');
|
console.log('✅ Terrain and decorations generated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
// DAMAGE / INTERACTION LOGIC
|
|
||||||
damageDecoration(x, y, amount) {
|
damageDecoration(x, y, amount) {
|
||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
const decor = this.decorationsMap.get(key);
|
const decor = this.decorationsMap.get(key);
|
||||||
|
|
||||||
if (!decor) return false;
|
if (!decor) return false;
|
||||||
|
|
||||||
decor.hp -= amount;
|
decor.hp -= amount;
|
||||||
|
|
||||||
// Visual feedback (flash red)
|
|
||||||
if (this.visibleDecorations.has(key)) {
|
if (this.visibleDecorations.has(key)) {
|
||||||
const sprite = this.visibleDecorations.get(key);
|
const sprite = this.visibleDecorations.get(key);
|
||||||
sprite.setTint(0xff0000);
|
sprite.setTint(0xff0000);
|
||||||
this.scene.time.delayedCall(100, () => sprite.clearTint());
|
this.scene.time.delayedCall(100, () => sprite.clearTint());
|
||||||
|
|
||||||
// Shake effect?
|
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: sprite,
|
targets: sprite,
|
||||||
x: sprite.x + 2,
|
x: sprite.x + 2,
|
||||||
@@ -449,17 +358,14 @@ class TerrainSystem {
|
|||||||
this.removeDecoration(x, y);
|
this.removeDecoration(x, y);
|
||||||
return 'destroyed';
|
return 'destroyed';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'hit';
|
return 'hit';
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDecoration(x, y) {
|
removeDecoration(x, y) {
|
||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
const decor = this.decorationsMap.get(key);
|
const decor = this.decorationsMap.get(key);
|
||||||
|
|
||||||
if (!decor) return;
|
if (!decor) return;
|
||||||
|
|
||||||
// Remove visual
|
|
||||||
if (this.visibleDecorations.has(key)) {
|
if (this.visibleDecorations.has(key)) {
|
||||||
const sprite = this.visibleDecorations.get(key);
|
const sprite = this.visibleDecorations.get(key);
|
||||||
sprite.setVisible(false);
|
sprite.setVisible(false);
|
||||||
@@ -467,60 +373,38 @@ class TerrainSystem {
|
|||||||
this.visibleDecorations.delete(key);
|
this.visibleDecorations.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove data
|
|
||||||
this.decorationsMap.delete(key);
|
this.decorationsMap.delete(key);
|
||||||
|
|
||||||
// Remove from array (slow but needed for save compat for now)
|
|
||||||
const index = this.decorations.indexOf(decor);
|
const index = this.decorations.indexOf(decor);
|
||||||
if (index > -1) this.decorations.splice(index, 1);
|
if (index > -1) this.decorations.splice(index, 1);
|
||||||
|
if (this.tiles[y] && this.tiles[y][x]) this.tiles[y][x].hasDecoration = false;
|
||||||
|
|
||||||
// Update tile flag
|
return decor.type;
|
||||||
if (this.tiles[y] && this.tiles[y][x]) {
|
|
||||||
this.tiles[y][x].hasDecoration = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return decor.type; // Return type for dropping loot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
placeStructure(x, y, structureType) {
|
placeStructure(x, y, structureType) {
|
||||||
if (this.decorationsMap.has(`${x},${y}`)) return false;
|
if (this.decorationsMap.has(`${x},${y}`)) return false;
|
||||||
|
|
||||||
const decorData = {
|
const decorData = {
|
||||||
gridX: x,
|
gridX: x,
|
||||||
gridY: y,
|
gridY: y,
|
||||||
type: structureType, // 'struct_fence', etc.
|
type: structureType,
|
||||||
id: `${x},${y}`,
|
id: `${x},${y}`,
|
||||||
maxHp: 5,
|
maxHp: 5,
|
||||||
hp: 5
|
hp: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add to data
|
|
||||||
this.decorations.push(decorData);
|
this.decorations.push(decorData);
|
||||||
this.decorationsMap.set(decorData.id, decorData);
|
this.decorationsMap.set(decorData.id, decorData);
|
||||||
|
|
||||||
// Update tile
|
|
||||||
const tile = this.getTile(x, y);
|
const tile = this.getTile(x, y);
|
||||||
if (tile) tile.hasDecoration = true;
|
if (tile) tile.hasDecoration = true;
|
||||||
|
this.lastCullX = -9999;
|
||||||
// Force Visual Update immediately?
|
|
||||||
// updateCulling will catch it on next frame, but to be safe:
|
|
||||||
this.lastCullX = -9999; // Force update
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Dynamic Tile Modification ---
|
|
||||||
|
|
||||||
setTileType(x, y, typeName) {
|
setTileType(x, y, typeName) {
|
||||||
if (!this.tiles[y] || !this.tiles[y][x]) return;
|
if (!this.tiles[y] || !this.tiles[y][x]) return;
|
||||||
|
|
||||||
const typeDef = Object.values(this.terrainTypes).find(t => t.name === typeName);
|
const typeDef = Object.values(this.terrainTypes).find(t => t.name === typeName);
|
||||||
if (!typeDef) return;
|
if (!typeDef) return;
|
||||||
|
|
||||||
this.tiles[y][x].type = typeName;
|
this.tiles[y][x].type = typeName;
|
||||||
this.tiles[y][x].texture = typeDef.texture;
|
this.tiles[y][x].texture = typeDef.texture;
|
||||||
|
|
||||||
// Force visual update if visible
|
|
||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
if (this.visibleTiles.has(key)) {
|
if (this.visibleTiles.has(key)) {
|
||||||
const sprite = this.visibleTiles.get(key);
|
const sprite = this.visibleTiles.get(key);
|
||||||
@@ -532,15 +416,12 @@ class TerrainSystem {
|
|||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
this.cropsMap.set(key, cropData);
|
this.cropsMap.set(key, cropData);
|
||||||
this.tiles[y][x].hasCrop = true;
|
this.tiles[y][x].hasCrop = true;
|
||||||
this.tiles[y][x].hasCrop = true;
|
|
||||||
// updateCulling loop will pick it up on next frame but we force it
|
|
||||||
this.lastCullX = -9999;
|
this.lastCullX = -9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCrop(x, y) {
|
removeCrop(x, y) {
|
||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
if (this.cropsMap.has(key)) {
|
if (this.cropsMap.has(key)) {
|
||||||
// Remove visual
|
|
||||||
if (this.visibleCrops.has(key)) {
|
if (this.visibleCrops.has(key)) {
|
||||||
const sprite = this.visibleCrops.get(key);
|
const sprite = this.visibleCrops.get(key);
|
||||||
sprite.setVisible(false);
|
sprite.setVisible(false);
|
||||||
@@ -560,35 +441,16 @@ class TerrainSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize rendering (called once)
|
|
||||||
init(offsetX, offsetY) {
|
init(offsetX, offsetY) {
|
||||||
this.offsetX = offsetX;
|
this.offsetX = offsetX;
|
||||||
this.offsetY = offsetY;
|
this.offsetY = offsetY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update culling (called every frame)
|
|
||||||
updateCulling(camera) {
|
updateCulling(camera) {
|
||||||
// Throttling Optimization - TEMPORARILY DISABLED FOR DEBUG
|
// Simple Culling
|
||||||
// const dist = Phaser.Math.Distance.Between(camera.scrollX, camera.scrollY, this.lastCullX, this.lastCullY);
|
|
||||||
// if (dist < 50) return;
|
|
||||||
|
|
||||||
// Debug log once
|
|
||||||
if (!this.hasLogged) {
|
|
||||||
console.log('UpdateCulling running. Camera:', camera.scrollX, camera.scrollY);
|
|
||||||
this.hasLogged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastCullX = camera.scrollX;
|
|
||||||
this.lastCullY = camera.scrollY;
|
|
||||||
|
|
||||||
// ... (rest of setup)
|
|
||||||
|
|
||||||
const view = camera.worldView;
|
const view = camera.worldView;
|
||||||
// Optimization: Adjust buffer based on View Distance setting
|
|
||||||
let buffer = 200;
|
let buffer = 200;
|
||||||
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') {
|
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;
|
||||||
buffer = 50;
|
|
||||||
}
|
|
||||||
const left = view.x - buffer - this.offsetX;
|
const left = view.x - buffer - this.offsetX;
|
||||||
const top = view.y - buffer - this.offsetY;
|
const top = view.y - buffer - this.offsetY;
|
||||||
const right = view.x + view.width + buffer - this.offsetX;
|
const right = view.x + view.width + buffer - this.offsetX;
|
||||||
@@ -604,12 +466,6 @@ class TerrainSystem {
|
|||||||
const minGridY = Math.floor(Math.min(p1.y, p2.y, p3.y, p4.y));
|
const minGridY = Math.floor(Math.min(p1.y, p2.y, p3.y, p4.y));
|
||||||
const maxGridY = Math.ceil(Math.max(p1.y, p2.y, p3.y, p4.y));
|
const maxGridY = Math.ceil(Math.max(p1.y, p2.y, p3.y, p4.y));
|
||||||
|
|
||||||
// Debug bounds once
|
|
||||||
if (!this.hasLoggedBounds) {
|
|
||||||
console.log('Culling Bounds:', minGridX, maxGridX, minGridY, maxGridY);
|
|
||||||
this.hasLoggedBounds = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startX = Math.max(0, minGridX);
|
const startX = Math.max(0, minGridX);
|
||||||
const endX = Math.min(this.width, maxGridX);
|
const endX = Math.min(this.width, maxGridX);
|
||||||
const startY = Math.max(0, minGridY);
|
const startY = Math.max(0, minGridY);
|
||||||
@@ -621,119 +477,68 @@ class TerrainSystem {
|
|||||||
|
|
||||||
for (let y = startY; y < endY; y++) {
|
for (let y = startY; y < endY; y++) {
|
||||||
for (let x = startX; x < endX; x++) {
|
for (let x = startX; x < endX; x++) {
|
||||||
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
// TILES
|
||||||
const key = `${x},${y}`;
|
const key = `${x},${y}`;
|
||||||
neededKeys.add(key);
|
neededKeys.add(key);
|
||||||
|
|
||||||
// Tile Logic
|
if (!this.visibleTiles.has(key)) {
|
||||||
if (!this.visibleTiles.has(key)) {
|
const tile = this.tiles[y][x];
|
||||||
const tilePos = this.iso.toScreen(x, y);
|
const screenPos = this.iso.toScreen(x, y);
|
||||||
const tileData = this.tiles[y][x];
|
const sprite = this.tilePool.get();
|
||||||
|
|
||||||
// Get from Pool
|
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
|
||||||
const sprite = this.tilePool.get();
|
sprite.setTexture(tile.texture);
|
||||||
sprite.setTexture(tileData.texture);
|
sprite.setDepth(this.iso.getDepth(x, y));
|
||||||
|
this.visibleTiles.set(key, sprite);
|
||||||
|
}
|
||||||
|
|
||||||
// Elevation effect
|
// DECORATIONS
|
||||||
const elevationOffset = tileData.elevation * -25;
|
const decor = this.decorationsMap.get(key);
|
||||||
sprite.setPosition(
|
if (decor) {
|
||||||
tilePos.x + this.offsetX,
|
neededDecorKeys.add(key);
|
||||||
tilePos.y + this.offsetY + elevationOffset
|
if (!this.visibleDecorations.has(key)) {
|
||||||
);
|
const sprite = this.decorationPool.get();
|
||||||
|
const screenPos = this.iso.toScreen(x, y);
|
||||||
|
|
||||||
// Senčenje
|
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
|
||||||
if (tileData.type.includes('grass')) {
|
// Fix for house sprite
|
||||||
let brightness = 1.0;
|
if (decor.type.includes('house_sprite') || decor.type.includes('market_sprite')) {
|
||||||
if (tileData.elevation > 0.5) {
|
sprite.setTexture(decor.type);
|
||||||
brightness = 1.0 + (tileData.elevation - 0.5) * 1.0;
|
sprite.setScale(decor.scale || 1.0);
|
||||||
} else {
|
|
||||||
brightness = 0.7 + tileData.elevation * 0.6;
|
|
||||||
}
|
|
||||||
sprite.setTint(Phaser.Display.Color.GetColor(
|
|
||||||
Math.min(255, Math.floor(92 * brightness)),
|
|
||||||
Math.min(255, Math.floor(184 * brightness)),
|
|
||||||
Math.min(255, Math.floor(92 * brightness))
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
sprite.clearTint();
|
sprite.setTexture(decor.type);
|
||||||
|
sprite.setScale(decor.scale || 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED DEPTH FOR DEBUG
|
// Origin adjusted for buildings
|
||||||
sprite.setDepth(-1000);
|
if (decor.type.includes('house') || decor.type.includes('market')) {
|
||||||
sprite.setVisible(true);
|
sprite.setOrigin(0.5, 0.8); // Adjusted origin for buildings
|
||||||
|
} else {
|
||||||
|
sprite.setOrigin(0.5, 1);
|
||||||
|
}
|
||||||
|
|
||||||
this.visibleTiles.set(key, sprite);
|
sprite.setDepth(this.iso.getDepth(x, y) + 1); // Above tile
|
||||||
|
this.visibleDecorations.set(key, sprite);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Crop Logic
|
// CROPS
|
||||||
if (this.tiles[y][x].hasCrop) {
|
const crop = this.cropsMap.get(key);
|
||||||
neededCropKeys.add(key);
|
if (crop) {
|
||||||
if (!this.visibleCrops.has(key)) {
|
neededCropKeys.add(key);
|
||||||
const cropData = this.cropsMap.get(key);
|
if (!this.visibleCrops.has(key)) {
|
||||||
if (cropData) {
|
const sprite = this.cropPool.get();
|
||||||
const cropPos = this.iso.toScreen(x, y);
|
const screenPos = this.iso.toScreen(x, y);
|
||||||
const tileData = this.tiles[y][x];
|
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
|
||||||
const elevationOffset = tileData.elevation * -25;
|
sprite.setTexture(`crop_stage_${crop.stage}`);
|
||||||
|
sprite.setDepth(this.iso.getDepth(x, y) + 0.5);
|
||||||
const sprite = this.cropPool.get();
|
this.visibleCrops.set(key, sprite);
|
||||||
sprite.setTexture(`crop_stage_${cropData.stage}`);
|
|
||||||
sprite.setPosition(
|
|
||||||
cropPos.x + this.offsetX,
|
|
||||||
cropPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset
|
|
||||||
);
|
|
||||||
// Crop depth = Y pos
|
|
||||||
const depth = this.iso.getDepth(x, y);
|
|
||||||
sprite.setDepth(depth);
|
|
||||||
|
|
||||||
this.visibleCrops.set(key, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoration Logic
|
|
||||||
if (this.tiles[y][x].hasDecoration) {
|
|
||||||
neededDecorKeys.add(key);
|
|
||||||
|
|
||||||
if (!this.visibleDecorations.has(key)) {
|
|
||||||
// Fast lookup from map
|
|
||||||
const decor = this.decorationsMap.get(key);
|
|
||||||
const tileData = this.tiles[y][x];
|
|
||||||
const elevationOffset = tileData.elevation * -25;
|
|
||||||
|
|
||||||
if (decor) {
|
|
||||||
const decorPos = this.iso.toScreen(x, y);
|
|
||||||
const sprite = this.decorationPool.get();
|
|
||||||
sprite.setTexture(decor.type);
|
|
||||||
|
|
||||||
// Apply same elevation offset as tile
|
|
||||||
sprite.setPosition(
|
|
||||||
decorPos.x + this.offsetX,
|
|
||||||
decorPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
const depth = this.iso.getDepth(x, y);
|
|
||||||
// Depth strategy: Base of object sorting.
|
|
||||||
// Add small offset based on type if needed, but mainly use Y
|
|
||||||
sprite.setDepth(depth);
|
|
||||||
|
|
||||||
// Apply scale if present
|
|
||||||
if (decor.scale) sprite.setScale(decor.scale);
|
|
||||||
else sprite.setScale(1);
|
|
||||||
|
|
||||||
sprite.flipX = (x + y) % 2 === 0;
|
|
||||||
|
|
||||||
// Sprites are just visual now, interaction handled by InteractionSystem via grid
|
|
||||||
// sprite.setInteractive(...) removed to fix conflicts
|
|
||||||
|
|
||||||
this.visibleDecorations.set(key, sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup invisible tiles
|
// Cleanup tiles
|
||||||
for (const [key, sprite] of this.visibleTiles) {
|
for (const [key, sprite] of this.visibleTiles) {
|
||||||
if (!neededKeys.has(key)) {
|
if (!neededKeys.has(key)) {
|
||||||
sprite.setVisible(false);
|
sprite.setVisible(false);
|
||||||
@@ -742,7 +547,7 @@ class TerrainSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup invisible decorations
|
// Cleanup decorations
|
||||||
for (const [key, sprite] of this.visibleDecorations) {
|
for (const [key, sprite] of this.visibleDecorations) {
|
||||||
if (!neededDecorKeys.has(key)) {
|
if (!neededDecorKeys.has(key)) {
|
||||||
sprite.setVisible(false);
|
sprite.setVisible(false);
|
||||||
@@ -751,7 +556,7 @@ class TerrainSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup visible crops
|
// Cleanup crops
|
||||||
for (const [key, sprite] of this.visibleCrops) {
|
for (const [key, sprite] of this.visibleCrops) {
|
||||||
if (!neededCropKeys.has(key)) {
|
if (!neededCropKeys.has(key)) {
|
||||||
sprite.setVisible(false);
|
sprite.setVisible(false);
|
||||||
@@ -760,21 +565,4 @@ class TerrainSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
getTerrainType(value) {
|
|
||||||
for (const type of Object.values(this.terrainTypes)) {
|
|
||||||
if (value < type.threshold) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.terrainTypes.STONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTile(gridX, gridY) {
|
|
||||||
if (gridX >= 0 && gridX < this.width && gridY >= 0 && gridY < this.height) {
|
|
||||||
return this.tiles[gridY][gridX];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
107
src/utils/ErrorHandler.js
Normal file
107
src/utils/ErrorHandler.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Global Error Handler
|
||||||
|
// Ujame kritične napake in prikaže prijazen "Crash Screen" namesto belega zaslona.
|
||||||
|
|
||||||
|
export class ErrorHandler {
|
||||||
|
static init() {
|
||||||
|
window.onerror = (message, source, lineno, colno, error) => {
|
||||||
|
ErrorHandler.showError(message, source, lineno, colno, error);
|
||||||
|
// return true; // Če vrnemo true, zavremo izpis v konzolo (ne želimo tega)
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
ErrorHandler.showError('Unhandled Promise Rejection', '', 0, 0, event.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🛡️ Global Error Handler Initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
static showError(message, source, lineno, colno, error) {
|
||||||
|
console.error('🔥 CRITICAL ERROR HAUGHT:', message);
|
||||||
|
|
||||||
|
// Prepreči podvajanje overlayev
|
||||||
|
if (document.getElementById('error-overlay')) return;
|
||||||
|
|
||||||
|
// Ustvari overlay element
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.id = 'error-overlay';
|
||||||
|
overlay.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
background-color: rgba(20, 0, 0, 0.95);
|
||||||
|
color: #ffaaaa;
|
||||||
|
z-index: 999999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const title = document.createElement('h1');
|
||||||
|
title.innerText = '💀 OOPS! GAME CRASHED 💀';
|
||||||
|
title.style.color = '#ff4444';
|
||||||
|
title.style.marginBottom = '20px';
|
||||||
|
|
||||||
|
const msgBox = document.createElement('div');
|
||||||
|
msgBox.innerText = `${message}\n\nFile: ${source}\nLine: ${lineno}:${colno}\n\n${error ? error.stack : ''}`;
|
||||||
|
msgBox.style.cssText = `
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #ff4444;
|
||||||
|
max-width: 800px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: #fff;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const btnContainer = document.createElement('div');
|
||||||
|
|
||||||
|
const btnReload = document.createElement('button');
|
||||||
|
btnReload.innerText = '🔄 RELOAD GAME';
|
||||||
|
btnReload.style.cssText = `
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #44aa44;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 15px;
|
||||||
|
`;
|
||||||
|
btnReload.onclick = () => window.location.reload();
|
||||||
|
|
||||||
|
const btnIgnore = document.createElement('button');
|
||||||
|
btnIgnore.innerText = 'IGNORE & TRY TO CONTINUE';
|
||||||
|
btnIgnore.style.cssText = `
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: #666;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
`;
|
||||||
|
btnIgnore.onclick = () => {
|
||||||
|
overlay.remove();
|
||||||
|
console.log('⚠️ User ignored critical error.');
|
||||||
|
};
|
||||||
|
|
||||||
|
btnContainer.appendChild(btnReload);
|
||||||
|
btnContainer.appendChild(btnIgnore);
|
||||||
|
|
||||||
|
overlay.appendChild(title);
|
||||||
|
overlay.appendChild(msgBox);
|
||||||
|
overlay.appendChild(btnContainer);
|
||||||
|
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -571,6 +571,49 @@ class TextureGenerator {
|
|||||||
ctx.lineTo(32, 40);
|
ctx.lineTo(32, 40);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
|
} else if (type === 'market') {
|
||||||
|
// Market Stall
|
||||||
|
// Wooden base
|
||||||
|
ctx.fillStyle = '#8B4513';
|
||||||
|
ctx.fillRect(4, 30, 56, 34); // Big base
|
||||||
|
|
||||||
|
// Counter
|
||||||
|
ctx.fillStyle = '#DEB887'; // Burlywood
|
||||||
|
ctx.fillRect(0, 40, 64, 10);
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
ctx.fillStyle = '#5C4033';
|
||||||
|
ctx.fillRect(4, 10, 4, 30);
|
||||||
|
ctx.fillRect(56, 10, 4, 30);
|
||||||
|
|
||||||
|
// Striped Roof
|
||||||
|
ctx.fillStyle = '#FF0000'; // Red
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(32, 0);
|
||||||
|
ctx.lineTo(64, 15);
|
||||||
|
ctx.lineTo(0, 15);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// White stripes
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(32, 0);
|
||||||
|
ctx.lineTo(40, 15);
|
||||||
|
ctx.lineTo(32, 15);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(32, 0);
|
||||||
|
ctx.lineTo(24, 15);
|
||||||
|
ctx.lineTo(16, 15);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Goods (Apples/Items)
|
||||||
|
ctx.fillStyle = '#00FF00';
|
||||||
|
ctx.beginPath(); ctx.arc(10, 38, 4, 0, Math.PI * 2); ctx.fill();
|
||||||
|
ctx.fillStyle = '#FF0000';
|
||||||
|
ctx.beginPath(); ctx.arc(20, 38, 4, 0, Math.PI * 2); ctx.fill();
|
||||||
|
|
||||||
} else if (type === 'ruin') {
|
} else if (type === 'ruin') {
|
||||||
// Isometric Ruin
|
// Isometric Ruin
|
||||||
|
|
||||||
@@ -922,5 +965,29 @@ class TextureGenerator {
|
|||||||
|
|
||||||
canvas.refresh();
|
canvas.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7. BONE
|
||||||
|
if (!scene.textures.exists('item_bone')) {
|
||||||
|
const size = 32;
|
||||||
|
const canvas = scene.textures.createCanvas('item_bone', size, size);
|
||||||
|
const ctx = canvas.getContext();
|
||||||
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
|
||||||
|
// Bone shape
|
||||||
|
ctx.strokeStyle = '#E8E8E8'; // Off-white
|
||||||
|
ctx.lineWidth = 6;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(10, 10);
|
||||||
|
ctx.lineTo(22, 22);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Knobs
|
||||||
|
ctx.fillStyle = '#E8E8E8';
|
||||||
|
ctx.beginPath(); ctx.arc(10, 10, 4, 0, Math.PI * 2); ctx.fill();
|
||||||
|
ctx.beginPath(); ctx.arc(22, 22, 4, 0, Math.PI * 2); ctx.fill();
|
||||||
|
|
||||||
|
canvas.refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user