🔥 FAZE SPRINT 1-3 COMPLETION: DayNight, Inventory, Core Farming System, Mrtvi Zombiji (ShamblerAI & Alfa Moć Taming), Sredili assets & daily UI
BIN
nova farma TRAE/.DS_Store
vendored
BIN
nova farma TRAE/assets/.DS_Store
vendored
BIN
nova farma TRAE/assets/DEMO_FAZA1/.DS_Store
vendored
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 648 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_sheet.png
Normal file
|
After Width: | Height: | Size: 578 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage0.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage1.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage2.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage3.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage4.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_cannabis_stage5.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_sheet.png
Normal file
|
After Width: | Height: | Size: 565 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_stage0.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_stage1.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_stage2.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_stage3.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_carrot_stage4.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_wheat_sheet.png
Normal file
|
After Width: | Height: | Size: 517 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_wheat_stage0.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_wheat_stage1.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_wheat_stage2.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/crop_wheat_stage3.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Crops/tilled_soil.png
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Structures/statue_200days.png
Executable file
|
After Width: | Height: | Size: 49 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Trees/drevo_faza_2.png
Normal file → Executable file
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 61 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Trees/drevo_veliko.png
Normal file → Executable file
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 146 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/UI/menu_background.png
Normal file
|
After Width: | Height: | Size: 612 KiB |
@@ -0,0 +1,42 @@
|
|||||||
|
# ⏱️ DEVLOG IN DNEVNIK DELA
|
||||||
|
**Datum:** 3. Marec 2026
|
||||||
|
**Zadeva:** Nova Farma Demo - Sprint 1, 2, 3 in Asseti (Zaključek dneva)
|
||||||
|
**Status:** ✅ FAZA DEMO 65% COMPLETION
|
||||||
|
|
||||||
|
## 📝 Povzetek današnjega dela
|
||||||
|
|
||||||
|
Danes smo opravili masiven premik naprej v razvoju "Nova Farma" DEMO verzije, pri čemer smo v celoti zaključili prve tri sprinte od petih, ki so potrebni za izdajo Demo verzije igre.
|
||||||
|
|
||||||
|
### 🎯 Kaj je bilo narejeno:
|
||||||
|
|
||||||
|
**✅ SPRINT 1 — "Osnove Preživetja" (100% DONE)**
|
||||||
|
- Dodan `DayNightSystem.js` (ambientna svetloba, zvezde na nočnem nebu).
|
||||||
|
- Implementirano spanje (tipka `[Z]` ob postelji/vreči za preskok noči do 06:00 zjutraj).
|
||||||
|
- Čas in dnevi sedaj dejansko tečejo in se avtomatsko sinhronizirajo z `UIScene`.
|
||||||
|
- Ustvarjen `InventorySystem.js` s funkcionalnim 7-slot hotbarom (tipke `[1]-[7]` ali kolešček na miški).
|
||||||
|
- Dodeljeni začetni starter itemi (Motika, Zalivalka, Semena: Pšenica in Korenje).
|
||||||
|
|
||||||
|
**✅ SPRINT 2 — "Kmetovanje" (100% DONE)**
|
||||||
|
- Refaktoriran `FarmingSystem.js` za uporabo Phaser Graphics za natančne 48x24px pikselske tilled soil (poorana tla) ploščice brez popačenj.
|
||||||
|
- Urejeno sajenje semen iz inventarja.
|
||||||
|
- Urejen `WaterSystem.js` z zbiralnikom deževnice (Rain Catcher), kjer Kai zajema vodo (tipka `[F]`) in zaliva sode `[E]`.
|
||||||
|
- Implementirana vizualna metrika rasti in odvisnost rasti rastlin od časa (`onNewDay`) in vode.
|
||||||
|
- Razrešeni ERR_FILE_NOT_FOUND preloading manjki glede dreves in uveljavljen pixel-perfect z-index sistem rastlinstva.
|
||||||
|
|
||||||
|
**✅ SPRINT 3 — "Zombi AI Sistem" (100% DONE)**
|
||||||
|
- Generiran nov `zombie_shambler_walk_sheet.png` preko AI v Dark Chibi Gothic stilu (4 smeri x 4 frames).
|
||||||
|
- Skripta je samodejno odstranila belo ozadje in pripravila sheet za engine.
|
||||||
|
- Skripta je uspešno skopirala že obstoječe in narejene dobre zombije iz starega repozitorija v trenutnega (`assets/DEMO_FAZA1/Characters`).
|
||||||
|
- Narejen modul `ZombieSystem.js`. Zombiji sedaj prespawnajo ponoči na robovih otoka.
|
||||||
|
- Zombi AI logika urejena: Wandering (naključno), Chasing (zasleduje Kaia).
|
||||||
|
- Dodan sistem **Alfa Moč**: ko se igralec približa nezavestnemu zombiju in drži presledek (`[SPACE]`), vizualna "progress bar" koda (koordinate, taming bar) igralcu prepusti udomačenega zombija z zlatim vizualnim partikli efektom.
|
||||||
|
|
||||||
|
### 📦 Status Datotek in Sistemski Pregled:
|
||||||
|
Posodobili smo `STATUS_DEMO_FAZA1_2026_03_03.md`, ki sedaj kaže na uradnih 65% preboja k izidu samega dema.
|
||||||
|
|
||||||
|
## 🚀 Naslednji koraki (za naslednjo sejo):
|
||||||
|
- **Sprint 4 (Zgodba):** 20 polaroidnih story slik z AI, uvoz in vzpostavitev `IntroScene.js`. Dodatek "Amnesia" glitches na ekran ob začetku.
|
||||||
|
- **Sprint 5 (Poliranje):** Generacija FX soundboarda - dež (web audio noise), stopinje in ambientni zvoki. Postavitev Demo menija in ureditve za Steam/Kickstarter predogled.
|
||||||
|
- **Dialog System:** Typewriter interakcija z NPC-jem (Gronk) za mali tutorial v igri.
|
||||||
|
|
||||||
|
Dnevnik končan in sistem prepuščen v roke git repozitorija.
|
||||||
404
nova farma TRAE/dokumentacija/GEMINI_FAZE_KOMPLET_2026_03_03.md
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
# 🎮 NOVA FARMA — KOMPLETNA STRUKTURA FAZ
|
||||||
|
## Prompt za Gemini | Avtor: David Kotnik | 2026-03-03
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ KAJ JE IGRA (Hitri opis)
|
||||||
|
|
||||||
|
**Nova Farma (Krvava Žetev)** je dark survival farming RPG.
|
||||||
|
|
||||||
|
- **Protagonist:** Kai Marković, 14 let, Alpha Hybrid — ugriznjen od zombija, a ni postal eden. Dobi moč kontrolirati zombije.
|
||||||
|
- **Cilj:** Preživi, zgradi kmetijo, obnovi civilizacijo, najdi ugrabljeno sestro Ano.
|
||||||
|
- **Svet:** Post-apokaliptična Slovenija, leto 2084. Začneš na malem otoku, nato odkrivaš celinski svet.
|
||||||
|
- **Styl:** 2.5D Dark Chibi Noir. Hand-drawn ilustracija (NI pixel art). Gothic dark barve.
|
||||||
|
- **Platform:** Desktop (Electron + Phaser 3)
|
||||||
|
- **Inspiracija:** Stardew Valley × The Last of Us × Don't Starve × My Time at Portia
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ STRUKTURA SVET
|
||||||
|
|
||||||
|
```
|
||||||
|
[DEMO] Kmetijski Otok (8×8 area)
|
||||||
|
↓ po 200 dneh
|
||||||
|
[FAZA 1] Celoten Otok odkrit (Fog of War se dvigne)
|
||||||
|
↓ po nakupu igre
|
||||||
|
[FAZA 2+] Celinska Mapa: 27 Razrušenih Mest + 20 Biome-ov
|
||||||
|
↓ endgame
|
||||||
|
[FAZA 3+] Černobil, Ana reši, Finalni konec
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kmetijski Otok** = tvoja stalna baza/dom, nikoli ga ne zapustiš. Ampak v Fazi 2+ greš na celino in obnavljaš mesta.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 KOMPLETNIH 10 FAZ — TOČNA VSEBINA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🆓 DEMO — "The Hook" (Brezplačno)
|
||||||
|
|
||||||
|
**Cilj:** Preživi 200 dni → Achievement "Trmasti Preživeli" → Odkleni Fazo 1
|
||||||
|
|
||||||
|
**Svet:** Samo Farma (8×8 tilov) na kmetijskem otoku. Vse ostalo je Fog of War.
|
||||||
|
|
||||||
|
**Vsebina:**
|
||||||
|
- **Bivanje:** Spalna Vreča → Šotor (Hiša zaklenjena)
|
||||||
|
- **Orodja:** Samo Lesena (Tier 1)
|
||||||
|
- **Rastline:** Pšenica (3 dni), Korenje (4 dni), **Konoplja (7 dni — GLAVNI ZASLUŽEK!)**
|
||||||
|
- **Zombiji:** Max 3 Shamblerji. Samo sledijo, ne delajo. [SPACE] = Alfa Moč = Udomačiš.
|
||||||
|
- **Voda:** VSA voda je strupena. Edini vir = Rain Catcher (lovilec deževnice)
|
||||||
|
- **Zgodba:** 20 Polaroid intro, Twin Bond signali od Ane (1×/dan, 10% chance), 3 Flashback spomini
|
||||||
|
|
||||||
|
**Economy:** Konoplja = 200g/bud. Pšenica = 15g, Korenje = 40g. Brez konoplje ne preživiš.
|
||||||
|
|
||||||
|
**Save file se prenese v polno igro!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💰 FAZA 1 — "Early Access: Odprtje Sveta" (Nakup igre)
|
||||||
|
|
||||||
|
**Cilj:** Razišči celoten otok, upravljaj Zombi Workforce, popravi Ruševine
|
||||||
|
|
||||||
|
**Svet:** CELOTEN OTOK odkrit (Fog of War se dvigne). Izvidnik Zombi gre v meglo in odkriva svet.
|
||||||
|
|
||||||
|
**Vsebina:**
|
||||||
|
|
||||||
|
**🧟 Zombi Workforce (5 tipov):**
|
||||||
|
| Tip | Naloga | Max |
|
||||||
|
|-----|--------|-----|
|
||||||
|
| 🕵️ Scout (Izvidnik) | Gre v Fog, odkriva mapo, prinaša material | 1 |
|
||||||
|
| 🌾 Farmer (Kmet) | Zaliva, žanje | 3 |
|
||||||
|
| 🪓 Lumberjack (Gozdar) | Seka drevesa | 2 |
|
||||||
|
| ⛏️ Miner (Rudar) | Koplje rudo, Lv5+ najde Železo | 2 |
|
||||||
|
| 📦 Porter (Nosač) | Pobira Drop Boxe → v skrinje | 1 |
|
||||||
|
|
||||||
|
**Drop Box sistem:** Zombiji ne nosijo v tvoje skrinje direktno. Odlagajo v Drop Box → ti pobereš.
|
||||||
|
|
||||||
|
**🏚️ Ruševine (popravi jih!):**
|
||||||
|
- Garaža → Crafting hub (Motorka, Laksarca, Vodni Filter, Stiskalnica)
|
||||||
|
- Rastlinjak → Gobe (indoor), zimske rastline
|
||||||
|
- Hlev → Krave, Gronk, Susi
|
||||||
|
|
||||||
|
**🏠 Bivanje:** Lepena Koča (Faza 1 MAX)
|
||||||
|
|
||||||
|
**⛏️ Rudnik:** Baker, Premog, Glina, Železo (samo Rudar Lv5+!)
|
||||||
|
|
||||||
|
**🌱 Nove rastline:** 80+ vrst. Sončnice → Bio-olje. Konoplja full unlock. Gobe (Klet).
|
||||||
|
|
||||||
|
**🍄 Skrita Klet:**
|
||||||
|
- Vhod skrit (za omaro / pod preprogo)
|
||||||
|
- Grow Room: UV luči, Hidroponika, Police za Gobe
|
||||||
|
- The Lab: Predelava → Mind Expansion napitki
|
||||||
|
|
||||||
|
**🐔 Živali:** Kure (Kokošnjak), Krave (Hlev) — dostava v škatli
|
||||||
|
|
||||||
|
**👻 Oltar:** Zgradi Oltar + najdi Inženirsko uro (Marko) + Medaljon (Elena) → Duhova Starša se pojavita kot Force Ghost, dajata nasvete in permanentne buffe
|
||||||
|
|
||||||
|
**🧟 Gronk:** (Demo Locked — izjema: prvih 20 kupcev + Streamerji takoj!)
|
||||||
|
- Trol z roza dreadlocki, vape v ustih, ima psičko Susi (Jazbečarko)
|
||||||
|
- Mentor, govori v 3. osebi: "Gronk... pomaga šefu?"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏛️ FAZA 2 — "Town Restoration & Museum"
|
||||||
|
|
||||||
|
**Cilj:** Razišči celinski svet, obnovi 27 razrušenih mest, odpri Muzej
|
||||||
|
|
||||||
|
**Svet:** Celinska Mapa (prej vidna samo kmetija/otok). 27 Razrušenih Mest razkropljenih po mapi.
|
||||||
|
|
||||||
|
**🏚️ Town Restoration Loop:**
|
||||||
|
```
|
||||||
|
1. Explore Celino → Najdi Ruined Town
|
||||||
|
2. Poberi material (Les, Kamen, Železo) + čas (zombiji pomagajo)
|
||||||
|
3. Obnovi stavbe:
|
||||||
|
- Kovačnica → Ivan (Kovač) se vseli
|
||||||
|
- Pekarna → Marija (Pekarka) se vseli
|
||||||
|
- Klinika → Dr. Chen se vseli
|
||||||
|
...in tako naprej
|
||||||
|
4. Posodi Zombi Delavce NPCjem → dobijo pomoč
|
||||||
|
5. NPC-ji ti dajejo nagrade + zgodba clues o Ani
|
||||||
|
```
|
||||||
|
|
||||||
|
**27 Mest × 5-15 hiš = 300+ stavb za obnovo**
|
||||||
|
**180 NPC-jev skupaj čez vsa mesta**
|
||||||
|
|
||||||
|
**🏛️ Muzej:**
|
||||||
|
- Vodi ga Kustos (NPC)
|
||||||
|
- Doniraš redke predmete (Artifakti, Legendarne Ribe, Zlatega Hroščca...)
|
||||||
|
- Nagrada: Sova prinaša darila podnevi, Netopir ponoči
|
||||||
|
|
||||||
|
**🎣 Ribnik (domači):**
|
||||||
|
- Prej samo divje ribarjenje (ocean)
|
||||||
|
- Zdaj gradiš ribnik na kmetiji
|
||||||
|
- Vzreja rib za hrano in prodajo
|
||||||
|
|
||||||
|
**🔧 Napredni Stroji (iz Garaže naprej):**
|
||||||
|
- Avtomatska Zalivalka (Solar powered)
|
||||||
|
- Bio-Rafinerija (predelava odpadkov v gorivo)
|
||||||
|
- Konzervarna (hrana za dolgo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ FAZA 3 — "Elektrika & Avtomatizacija"
|
||||||
|
|
||||||
|
**Cilj:** Električno omrežje, avtomatska pridelava
|
||||||
|
|
||||||
|
**Vsebina:**
|
||||||
|
- **Power Grid:** Generator → Električni vodi → Naprave
|
||||||
|
- **Avtomatizacija:** Kombajn, Robotski kmet, Avtomatski packer
|
||||||
|
- **Energija:** Solar paneli, Vodni generator (ob reki)
|
||||||
|
- **Upgrade:** Vse prejšnje zgradbe dobijo električno varjanto (2× hitrost)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌀 FAZA 4 — "Portali & Biomi (Tier 1)"
|
||||||
|
|
||||||
|
**Cilj:** Odpri 9 Normalnih Biome-ov prek portalov
|
||||||
|
|
||||||
|
**9 Normalni Biomi:**
|
||||||
|
1. 🌾 Grassland — Travnik (Spawn area)
|
||||||
|
2. 🌲 Forest — Gozd (les, volkovi, jeleni)
|
||||||
|
3. 🌿 Swamp — Močvirje (strupeno)
|
||||||
|
4. 🏜️ Desert — Puščava (vročina, škorpijoni)
|
||||||
|
5. 🏔️ Mountain — Gora (rudnik, orli)
|
||||||
|
6. ❄️ Snow — Sneg (mraz, led medvedi)
|
||||||
|
7. 🏚️ Wasteland — Puščoba (ruševine, mutanti)
|
||||||
|
8. 🌴 Tropical — Tropski (plaža, kokoši)
|
||||||
|
9. ☢️ Radioactive — Radioaktivno (mutacije, sij)
|
||||||
|
|
||||||
|
**Za vsak biom:** Edinstvene rastline, živali, materiali, Boss, NPCji, Romance opcija
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🐉 FAZA 5 — "Anomalni Biomi (Tier 2)"
|
||||||
|
|
||||||
|
**Cilj:** Odpri 11 Posebnih/Anomalnih Biome-ov
|
||||||
|
|
||||||
|
**11 Anomalni Biomi:**
|
||||||
|
10. 🦖 Dino Valley — T-Rex, raptorji, dinozavri
|
||||||
|
11. 🐉 Mythical Highlands — Zmaji, Griffini, Enorogovi
|
||||||
|
12. 🌲 Endless Forest — Bigfoot, Wendigo, kriptidi
|
||||||
|
13. 🦕 Loch Ness — Nessie, Škotska, gradu
|
||||||
|
14. 💀 Catacombs — Skeletne armade, duhovi, Nekromancija
|
||||||
|
15. 🏺 Egyptian Desert — Piramide, Mumije, Sfinga
|
||||||
|
16. 🌴 Amazon Rainforest — Piranhe, Anaconde, Pleme
|
||||||
|
17. 🌊 Atlantis — Podvodni svet, Morske deklice, Podmornica
|
||||||
|
18. ☢️ Chernobyl — FINALNA ZONA, Ana je tu!
|
||||||
|
19. 🇲🇽 Mexican Cenotes — Aksolotli, Majevske ruševine
|
||||||
|
20. 🧙♀️ Witch Forest — Baba Jaga, Čarovnice, Magija
|
||||||
|
|
||||||
|
**Za Portal Unlock:** Vsak biom ima edinstven pogoj (npr. Atlantis = popravi potapljačko opremo + 7 kristalov)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💍 FAZA 6 — "Romanca, Otroci & Generacije"
|
||||||
|
|
||||||
|
**Cilj:** Vzpostavi družino, generacijsko igranje
|
||||||
|
|
||||||
|
**12 Romance Opcij:**
|
||||||
|
- Lena (Kmetova hčerka), Katarina (Trgovka), Sonya (Zdravnikova asistentka)...
|
||||||
|
- Eksotičnih 7: Plemenita Princesa (Amazon), Morska Deklica (Atlantis), Valkira (Mythical), Egipčanska Svečenica, Škotska Deklica, Čuvajka Dinozavrov, Duh Deklica (Catacombs)
|
||||||
|
|
||||||
|
**Otroci — 5 stopenj rasti:**
|
||||||
|
1. Dojenček (0-1) → Zibelka, skrb
|
||||||
|
2. Malček (1-3) → Hodi, igra
|
||||||
|
3. Otrok (3-10) → Pomaga na farmi!
|
||||||
|
4. Najstnik (10-18) → Polno delo
|
||||||
|
5. Odrasel (18+) → **Postane Playable!**
|
||||||
|
|
||||||
|
**Generacije:** Kai → otroci → vnuki → ... 100+ realnih let možno
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗️ FAZA 7 — "Mega Razvoj Mest"
|
||||||
|
|
||||||
|
**Cilj:** Nadgradi vsa 27 mest v polno mesti
|
||||||
|
|
||||||
|
**Town Upgrade System:**
|
||||||
|
```
|
||||||
|
Ruined → Repaired → Developed → Thriving → City
|
||||||
|
(Faza 2) (Faza 7) (Endgame)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mega Projekti:**
|
||||||
|
- Elektrarna za celotno mesto
|
||||||
|
- Šola (uči otroke, +skills)
|
||||||
|
- Bolnišnica (zdravi NPC-je in Kai)
|
||||||
|
- Trg (teden tržnica, 50+ prodajalcev)
|
||||||
|
- Stara Dvorana (NPC odloča z Kai-em skupaj)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔬 FAZA 8 — "Research & Advanced Tech"
|
||||||
|
|
||||||
|
**Cilj:** Napredna tehnologija + Ana's Science System
|
||||||
|
|
||||||
|
**Ana (ko je rešena v Fazi 8):**
|
||||||
|
- Odpre Laboratorij na kmetiji
|
||||||
|
- Research tree: Bio-tech, Radiation tech, Nano-tech
|
||||||
|
- Crops 2.0 (genetsko modificirane, 2× yield)
|
||||||
|
- Zombie Evolution (zombiji dobijo upgrade)
|
||||||
|
|
||||||
|
**Skrita Klet FULL UPGRADE:**
|
||||||
|
- Industrijska Gojilnica (200× kapaciteta)
|
||||||
|
- Kemični Laboratorij (kompleksne formule)
|
||||||
|
- Tveganje: Policija/Župan kaznuje!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ☢️ FAZA 9 — "Chernobyl & The Rescue"
|
||||||
|
|
||||||
|
**Cilj:** Prodri v Černobil, reši Ano
|
||||||
|
|
||||||
|
**Zgodba:**
|
||||||
|
1. Kai najde vse 9 Key Fragmentov (iz biome bosšev)
|
||||||
|
2. Portal do Černobila se odklene
|
||||||
|
3. Fight through: Radiation Zones + Zmaj Volk boss (emotionalen)
|
||||||
|
4. Giant Troll King Phase 1 (HP: 2500) — premagan, beži
|
||||||
|
5. **ANA REŠENA** 💜
|
||||||
|
|
||||||
|
**The Reunion:**
|
||||||
|
```
|
||||||
|
Kai enters Ana's cell.
|
||||||
|
Ana turns around.
|
||||||
|
|
||||||
|
ANA: "...Kai?"
|
||||||
|
KAI: "Ana... I found you."
|
||||||
|
[They embrace, crying]
|
||||||
|
ANA: "You came for me..."
|
||||||
|
KAI: "Always. Twin Bond, remember?"
|
||||||
|
|
||||||
|
[Twin Bond FULLY AWAKENS — zlatem svit!]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ana postane Playable!** Unlock vseh 6 Twin Bond abilityjev.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌟 FAZA 10 — "Final Choice & 4 Endings" (ENDGAME)
|
||||||
|
|
||||||
|
**Cilj:** Premagi finale, izberi konec
|
||||||
|
|
||||||
|
**Dr. Krnić Ultimatum:** *"Pridi v Reactor Core. Ali spustim SUPER VIRUS. 7 dni."*
|
||||||
|
|
||||||
|
**Boss Rush:**
|
||||||
|
1. Dr. Krnić (HP: 1500)
|
||||||
|
2. Giant Troll King Phase 2 (HP: 5000 — Ultimate Form!)
|
||||||
|
|
||||||
|
**CURE MACHINE:**
|
||||||
|
```
|
||||||
|
Dr. Krnić (dying): "Edini način zdravila = Alpha Hybrid žrtev."
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 KONCI — Player Choice:**
|
||||||
|
|
||||||
|
| Konec | Pogoj | Kaj se zgodi |
|
||||||
|
|-------|-------|--------------|
|
||||||
|
| 💔 Kai se žrtvuje | Pritisni A | Kai umre, Ana vodi novi svet, hčer imenuje "Kai" |
|
||||||
|
| 💔 Ana se žrtvuje | Pritisni B | Ana umre, Kai vodi novi svet, hčer imenuje "Ana" |
|
||||||
|
| 💀 Dark World | Pritisni C | Oba zavrneta. Živita skupaj večno, zombiji ostanejo. |
|
||||||
|
| 🌟 PERFECT ✨ | Vse 50 Aninih Clues + 9 Key Fragmentov | Ana izračuna alternativo — oba preživita! |
|
||||||
|
|
||||||
|
**Perfect Ending zadnja scena:** Piknik na kmetiji. Kai, Ana, partnerji, 6 otrok, smeh.
|
||||||
|
```
|
||||||
|
KAI: "We did it."
|
||||||
|
ANA: "Together. Always."
|
||||||
|
[Children laughing in background]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 KOMPLETNI LIKI
|
||||||
|
|
||||||
|
| Lik | Starost | Vloga | Videz | Kdaj |
|
||||||
|
|-----|---------|-------|-------|------|
|
||||||
|
| **Kai Marković** | 14 | Protagonist, Alpha Hybrid | Pink/zeleni dreadlocki, rdeče oči, gaugi, nos ring, hoodie, cunje | Demo+ |
|
||||||
|
| **Ana Marković** | 14 | Ugrabljena sestra | Kratki temni lasje, bela lab majica, pametna, urejena | Faza 9 (rešena) |
|
||||||
|
| **Marko Marković** | ~45 | Mrtev oče → Duh | Sivi lasje, brada, halatna | Faza 1 (Oltar) |
|
||||||
|
| **Elena Marković** | ~43 | Mrtva mama → Duh | Dolgi temni lasje, topel nasmeh | Faza 1 (Oltar) |
|
||||||
|
| **Gronk** | ? | Trol pomočnik | Ogromen, roza dreadlocki, vape, Susi psička | Faza 1 |
|
||||||
|
| **Dr. Krnić** | ~60 | Zlobnež | Mad scientist, halatna, divji pogled | Faza 9-10 |
|
||||||
|
| **Giant Troll King** | ? | Ugrabil Ano | 3× Kai, oklopljen, rdeče oči | Faza 9-10 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 VIZUALNI STIL — PRAVILA (NIKOLI KRŠI)
|
||||||
|
|
||||||
|
1. **NI PIXEL ART** — High-res smooth ilustracija
|
||||||
|
2. **Thick black outlines** (~3-5px) na VSEH likih
|
||||||
|
3. **Gothic Dark Chibi** — veliki glave, majhna telesa, ekspresivne oči
|
||||||
|
4. **Muted gothic palette:** Temne modre, vijolične, rjave + neon accenti (teal, zelena, vijolična)
|
||||||
|
5. **Transparent ozadje** (Alpha PNG) za VSE like in objekte
|
||||||
|
6. **Kai DNA — NIKOLI NE MENJAJ:** Pink/zeleni dreadlocki, rdeče oči, gaugi, nos ring — to so konstante skozi VSO igro in VSE starosti
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 ŠTEVILKE
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-|-|
|
||||||
|
| **Faz skupaj** | DEMO + 10 FAZ |
|
||||||
|
| **Biome-ov** | 20 (9 Normal + 11 Anomalous) |
|
||||||
|
| **Boss-ov** | 24 (mini-boss/biome + Finalni) |
|
||||||
|
| **Mest** | 27 (po celinski mapi) |
|
||||||
|
| **NPC-jev** | 180 (čez vsa mesta) |
|
||||||
|
| **Romance opcij** | 12 |
|
||||||
|
| **Stopenj rasti otrok** | 5 |
|
||||||
|
| **Koncev** | 4 |
|
||||||
|
| **Zombi worker tipov** | 5 |
|
||||||
|
| **Rastlin** | 80+ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💬 KLJUČNI DIALOGJI (Kopiraj direktno)
|
||||||
|
|
||||||
|
**Twin Bond signali (Ana, šibki):**
|
||||||
|
```
|
||||||
|
"Kai... čutiš me? Sem ujeta... ampak ŽIVIM. Ne obupaj!"
|
||||||
|
"Troll me čuva... ampak nisem sama..."
|
||||||
|
"Kai... najdi me. Twin Bond... deluje..."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gronk:**
|
||||||
|
```
|
||||||
|
"Gronk... pomaga šefu?"
|
||||||
|
"Gronk... dober zombi?"
|
||||||
|
"Gronk... šef... žalosten? Gronk pomagal... najti sestro!"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Marko (Duh):**
|
||||||
|
```
|
||||||
|
"Kai... Sin... Utri ograjo. Zombiji so šibki, ampak horda..."
|
||||||
|
"Skupaj sva verjela v boljši svet. Ne nehaj verjeti."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Elena (Duh):**
|
||||||
|
```
|
||||||
|
"Rastline potrebujejo vodo, Kai. Kot ti ljubezen."
|
||||||
|
"Nisi kriv. Nikoli nisi bil kriv."
|
||||||
|
```
|
||||||
|
|
||||||
|
**The Reunion (Faza 9):**
|
||||||
|
```
|
||||||
|
ANA: "...Kai?"
|
||||||
|
KAI: "Ana... I found you."
|
||||||
|
ANA: "You came for me..."
|
||||||
|
KAI: "Always. Twin Bond, remember?"
|
||||||
|
ANA: "I felt you. Every day. Searching..."
|
||||||
|
[Twin Bond fully awakens — zlatem svit]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kai dnevnik:**
|
||||||
|
```
|
||||||
|
Dan 1: "Končno... Nov dom. Majhna kmetija, ampak dovolj za začetek."
|
||||||
|
Dan 30: "En mesec. Preživim. Ampak Ana... kje si?"
|
||||||
|
Dan 100: "Sto dni. Pol poti. Držim se."
|
||||||
|
Dan 200: "Dvesto dni. Zdaj se začne pravi boj."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*"Twin Bond. Zdaj in vedno."* 💜
|
||||||
|
*© 2026 HipoDevil666 | Nova Farma / Krvava Žetev*
|
||||||
|
*Posodobljeno: 2026-03-03 ob 14:54*
|
||||||
448
nova farma TRAE/dokumentacija/GEMINI_MASTER_BRIEF_2026_03_03.md
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
# 🎮 NOVA FARMA (KRVAVA ŽETEV) — KOMPLETNI MASTER BRIEF
|
||||||
|
## Za: Gemini / AI asistenta | Datum: 2026-03-03
|
||||||
|
## Avtor: David Kotnik (HipoDevil666)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ TL;DR (Preberi najprej!)
|
||||||
|
|
||||||
|
**Nova Farma** je dark survival farming RPG z zombie kontrolo, postavljeno v post-apokaliptično Slovenijo leta 2084. Igraš **Kai Markovića** (14 let), ki je edini preživeli alfa hibrid — ugriznjen od zombija, a ne postane eden. Namesto tega dobi moč **kontrolirati zombije**. Cilj: preživi, zgradi kmetijo, najdi ugrabljeno sestro Ano.
|
||||||
|
|
||||||
|
**Vizualni stil:** 2.5D Dark Chibi Noir — Hand-drawn ilustracija, NI pixel art. Clean lines, thick outlines, muted gothic barve.
|
||||||
|
|
||||||
|
**Platform:** Desktop (Electron + Phaser 3)
|
||||||
|
|
||||||
|
**Inspired by:** Stardew Valley × The Last of Us × Don't Starve × My Time at Portia
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 SVET
|
||||||
|
|
||||||
|
- **Leto:** 2084
|
||||||
|
- **Lokacija:** Slovenija, majhen skriti otok v dolini
|
||||||
|
- **Svet:** Otok (6400×6400px) sredi oceana (10400×10400px)
|
||||||
|
- **Atmosfera:** Post-apokalipsa. Civilizacija je padla. Narava se vrača. Zombiji povsod.
|
||||||
|
- **Vreme:** Dež (edini vir čiste vode!), sončno, nevihta — dinamično
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 LIKI (KOMPLET)
|
||||||
|
|
||||||
|
### 🧒 KAI MARKOVIĆ — Protagonist
|
||||||
|
- **Starost:** 14 let
|
||||||
|
- **Spol:** Moški
|
||||||
|
- **Videz:** Pink/zeleni dreadlocki, rdeče oči (genetska lastnost Marković linije), gaugi v ušesih, nos ring, majhni vampirski zobki. Hoodie, raztrgane kavbojke, cunje. Ruksak. Drži magično knjigo v eni roki.
|
||||||
|
- **Osebnost:** Tih, določen, redkobeseden — ko govori, je globoko. Ne joče pred drugimi. Skrbi se za druge bolj kot zase.
|
||||||
|
- **Posebnosti:**
|
||||||
|
- **Alpha Hybrid:** Ugriznjen a ni postal zombi. Imun.
|
||||||
|
- **Zombie Control (Alfa Moč):** [SPACE] → kontrolira zombije
|
||||||
|
- **Twin Bond:** Telepatija z Ano (čuti jo, dobiva šibke signale)
|
||||||
|
|
||||||
|
### 👧 ANA MARKOVIĆ — Sestra (UGRABLJENA)
|
||||||
|
- **Starost:** 14 let (Kai-eva dvojčica)
|
||||||
|
- **Status:** Ugrabljena od Giant Troll Kinga, zaprta v Černobilu
|
||||||
|
- **V igri:** Samo kot glas (Twin Bond signali) v Demo + Faza 1
|
||||||
|
- **Karakter:** Genialna, radovedna, optimistična. Nasprotje Kai-a.
|
||||||
|
- **Posebnost:** Njena genijskost bo rešila svet (alternativni konec)
|
||||||
|
|
||||||
|
### 👨🔬 MARKO MARKOVIĆ — Oče (MRTEV, Dan 3)
|
||||||
|
- **Videz:** Sredovečni znanstvenik s sivo-rjavimi lasmi, brada
|
||||||
|
- **Status:** Umrl od zombie ugrizov Dan 3, brani družino
|
||||||
|
- **V igri:** Duh (Force Ghost) — pojavi se ko Kai zgradi Oltar + najde Inženirsko uro
|
||||||
|
- **Vloga duha:** Inženirski nasveti, buff: +15% Craft hitrost
|
||||||
|
|
||||||
|
### 👩🔬 ELENA MARKOVIĆ — Mama (MRTVA, Dan 3)
|
||||||
|
- **Videz:** Visoka ženska, dolgi temni lasje, topel nasmeh
|
||||||
|
- **Status:** Umrla isti dan kot Marko
|
||||||
|
- **V igri:** Duh — pojavi se ko Kai najde Medaljon
|
||||||
|
- **Vloga duha:** Kmetijski nasveti, buff: +10% Energy regen na kmetiji
|
||||||
|
|
||||||
|
### 🧟 GRONK — Prvi zombi pomočnik (FAZA 1)
|
||||||
|
- **Tip:** Trol (udomačen velikan, ne navaden zombi)
|
||||||
|
- **Videz:** Ogromen, roza dreadlocki, vape v ustih (permanent). Vedno chill.
|
||||||
|
- **Osebnost:** "Chill stric". Govori počasi, v 3. osebi. Srčen pod grdo zunanjostjo.
|
||||||
|
- **Primer govora:** "Gronk... pomaga šefu? Gronk... dober zombi?"
|
||||||
|
- **Unlock:** Faza 1 (Demo: Locked — razen prvih 20 kupcev + streamerji)
|
||||||
|
- **Ima psa:** Susi (Jazbečarka) — spi pri njegovih nogah
|
||||||
|
|
||||||
|
### 👨🔬 DR. KRNIĆ — Glavni zlobnež
|
||||||
|
- **Starost:** ~60 let
|
||||||
|
- **Videz:** Mad scientist aesthetics — halatna, redki lasje, divji pogled
|
||||||
|
- **Dejanje:** NAMENOMA sprostil zombie virus iz Black Serpent Laboratory
|
||||||
|
- **Cilj:** Kontrola nad preostalim svetom
|
||||||
|
- **V igri:** Faza 2+, finalni boss fight (skupaj z Giant Troll Kingom)
|
||||||
|
|
||||||
|
### 🦍 GIANT TROLL KING — Ugrabil Ano
|
||||||
|
- **Videz:** Ogromen trol, 3× večji od Kai-a, oklopljen, rdeče oči
|
||||||
|
- **Stats:** HP 5000, seizmični napadi, kliče zombije, ognjevit dih
|
||||||
|
- **Lokacija:** Černobilski Reaktor (Faza 2+)
|
||||||
|
- **V Demu/Fazi 1:** Samo omenjeni
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎬 ZGODBA — AKT PO AKT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📼 PROLOG: DAN 0–30 (Pred igro — Intro sekvenca)
|
||||||
|
|
||||||
|
Prikaže se samo enkrat, ko prvič zagneš igro:
|
||||||
|
|
||||||
|
**20 Polaroid fotografij z typewriter komentarji:**
|
||||||
|
|
||||||
|
| # | Slika | Komentar |
|
||||||
|
|---|-------|----------|
|
||||||
|
| 1 | Srečna družina pred virusom | "Pred virusom... bili smo srečni." |
|
||||||
|
| 2 | Marko v laboratoriju | "Oče je delal na cepilu..." |
|
||||||
|
| 3 | Red Alert alarm | "Potem je prišel DAN 0..." |
|
||||||
|
| 4 | Kaos na ulicah | "Svet se je sesula v 48 urah." |
|
||||||
|
| 5 | Dom je obkoljen | "DAN 3: Prišli so k nam..." |
|
||||||
|
| 6 | Oče se bori | "Oče: 'Kai... varuj sestro... beži...'" |
|
||||||
|
| 7 | Mama pada | "Mama: 'Ana... Kai... ljubiva vaju...'" |
|
||||||
|
| 8 | Kai ugriznjen | "Ugriznili so me... ampak nisem postal zombi." |
|
||||||
|
| 9 | Ana ugrabljena | "Ana: 'KAIII! POMAGAJ!'" |
|
||||||
|
| 10 | Giant Troll King | "Nekega... POŠASTI jo je odnesel." |
|
||||||
|
| 11 | Kai sam | "Starši mrtvi. Sestra ugrabljena. Sam." |
|
||||||
|
| 12 | Alfa moč se zbudi | "Nekaj se je zbudilo v meni..." |
|
||||||
|
| 13 | Kai z zombijem | "Zombiji... me poslušajo?" |
|
||||||
|
| 14 | Pobeg iz mesta | "Moral sem pobegniti... preživeti..." |
|
||||||
|
| 15 | Potovanje | "7 dni pešačenja skozi peklo..." |
|
||||||
|
| 16 | Odkrita dolina | "Našel sem jo. Mojo dolino." |
|
||||||
|
| 17 | Zapuščena kmetija | "Majhna kmetija. Nov začetek." |
|
||||||
|
| 18 | Kai drži semena | "Če bom preživel... jo bom našel." |
|
||||||
|
| 19 | Twin Bond signal | "Čutim jo... Ana je ŽIVA." |
|
||||||
|
| 20 | Kai zapriseže | "Prisegli sem... Najti jo bom. Ne glede na ceno." |
|
||||||
|
|
||||||
|
**Po polaroidih:** Kai se prebudi v Spalni Vreči → blur efekt (amnezija) → Twin Bond signal → igra začne.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏝️ AKT 1: DEMO — "PREŽIVETJE" (Dan 1–200)
|
||||||
|
|
||||||
|
**Cilj:** Preživi 200 dni → Odkleni Fazo 1
|
||||||
|
|
||||||
|
**Gameplay Loop:**
|
||||||
|
```
|
||||||
|
Jutro → Zalij rastline → Zberi pridelek → Popravi/Zgradi
|
||||||
|
Podnevi → Research, Crafting, Explore otok
|
||||||
|
Zvečer → Pospravi, odnesi v skrinje, skozi dialog
|
||||||
|
Noč → Zpati v šotoru (ali Kai omedli = kazen)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zgodba teče skozi 3 kanale:**
|
||||||
|
|
||||||
|
1. **KAI DNEVNIK** — ob vsakem Sleep, Kai napiše en stavek:
|
||||||
|
- Dan 1: *"Končno... Nov dom. Majhna kmetija, ampak dovolj za začetek."*
|
||||||
|
- Dan 7: *"En teden sam. Twin Bond signal je bil šibak nocoj."*
|
||||||
|
- Dan 30: *"En mesec. Preživim. Kmetija raste. Ampak Ana... kje si?"*
|
||||||
|
- Dan 100: *"Sto dni. Pol poti. Ana... držim se."*
|
||||||
|
- Dan 200: *"Dvesto dni. Naredil sem, kar sem si obljubil. Zdaj se začne pravi boj."*
|
||||||
|
|
||||||
|
2. **TWIN BOND SIGNALI** — 1× na dan, 10% chance, šibek Anin glas:
|
||||||
|
- *"Kai... čutiš me? Sem ujeta... ampak ŽIVIM."*
|
||||||
|
- *"Ne obupaj... Twin Bond... deluje..."*
|
||||||
|
- *"Troll me čuva... ampak nisem sama..."*
|
||||||
|
- Visual: vijolični blisk na zaslonu, Kai zamrzne za sekundo
|
||||||
|
|
||||||
|
3. **FLASHBACK SPOMINI** — Kai stopi 80px blizu posebnih predmetov:
|
||||||
|
- Star foto → *"Srečni časi"* — Ana z metuljem
|
||||||
|
- Epruveta → *"Očetov laboratorij"* — "Nikoli ne dotikaj teh vzorčkov"
|
||||||
|
- Stara sveča → *"Zadnja večerja"* — Anin rojstni dan
|
||||||
|
|
||||||
|
**Omejitve v Demu:**
|
||||||
|
- Samo šotor (ne hiša)
|
||||||
|
- Samo Tier 1 orodja (lesena)
|
||||||
|
- Max 3 zombiji (ne morejo delati — samo sledijo)
|
||||||
|
- 2 rastlini: Pšenica (3 dni) + Korenje (4 dni)
|
||||||
|
- Voda samo iz dežja (Rain Catcher)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌫️ AKT 2: FAZA 1 — "ODPRTJE" (Dan 200+)
|
||||||
|
|
||||||
|
**Trigger:** 200 dni preživetih → Fog of War se začne dvigovati
|
||||||
|
|
||||||
|
**Nove mehanike:**
|
||||||
|
|
||||||
|
| Mehanika | Opis |
|
||||||
|
|----------|------|
|
||||||
|
| **Fog of War** | Svet okrog otoka je bil skrit. Zdaj se odkriva. |
|
||||||
|
| **Gronk Unlock** | Prvi pravi pomočnik (Trol, demo locked) |
|
||||||
|
| **Scout Zombi** | Edini zombi ki gre v Fog. Vrne se z materiali. |
|
||||||
|
| **Zombi Workforce** | 5 tipov zombi delavcev z leveli |
|
||||||
|
| **Ruševine** | Garaža, Rastlinjak, Hlev — najdeš in popraviš |
|
||||||
|
| **Rudnik** | Majhen rudnik — Baker, Premog, Glina |
|
||||||
|
| **Crafting napredek** | Motorka, Vodni Filter, Stiskalnica |
|
||||||
|
|
||||||
|
**Zombi Workforce (5 tipov):**
|
||||||
|
|
||||||
|
| Tip | HP | Hitrost | Naloga | Max | Level Up |
|
||||||
|
|-----|----|---------|--------|-----|----------|
|
||||||
|
| 🕵️ Izvidnik (Scout) | 50 | Hiter | Gre v Fog, odkriva mapo, prinaša material | 1 | Hitrejše iskanje, manj izgub |
|
||||||
|
| 🌾 Kmet (Farmer) | 30 | Počasen | Zaliva, žanje | 3 | Hitrejše, večji pridelek |
|
||||||
|
| 🪓 Gozdar (Lumberjack) | 40 | Normalen | Seka drevesa | 2 | Hitrejše, več lesa/drevo |
|
||||||
|
| ⛏️ Rudar (Miner) | 60 | Počasen | Koplje rudo | 2 | Lv5+ najde Železo! |
|
||||||
|
| 📦 Nosač (Porter) | 40 | Normalen | Pobira Drop Boxe → polaga v skrinje | 1 | Lv7+ nosi 20 predmetov |
|
||||||
|
|
||||||
|
**Drop Box sistem:**
|
||||||
|
- Zombiji ne nosijo direktno v tvoje skrinje (balance)
|
||||||
|
- Delo → Drop Box → Ti pobereš → Tvoj inventar
|
||||||
|
- 3 Drop Boxy: ob Workshopu, Rudniku, Farmi
|
||||||
|
|
||||||
|
**Parental Ghost Quest:**
|
||||||
|
```
|
||||||
|
Kai zgradi Oltar (20 Kamen + 10 Les + 2 Sveči)
|
||||||
|
+ Najde Inženirsko uro (Markov predmet) → Marko se pojavi
|
||||||
|
+ Najde Medaljon (Elenin predmet) → Elena se pojavi
|
||||||
|
|
||||||
|
Marko: "Kai... Sin... Utri ograjo."
|
||||||
|
Elena: "Rastline potrebujejo vodo, Kai. Kot ti ljubezen."
|
||||||
|
Buffi so stalni ko si na kmetiji.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔵 AKT 3: FAZA 2+ — "ISKANJE ANG" (Prihodnost)
|
||||||
|
|
||||||
|
**Unlock:** Faza 2 = Nova območja, portali do biome-ov
|
||||||
|
|
||||||
|
**20 Biome-ov:**
|
||||||
|
|
||||||
|
**Tier 1 — Normalni:**
|
||||||
|
Grassland, Forest, Swamp, Desert, Mountain, Snow, Wasteland, Tropical, Radioactive
|
||||||
|
|
||||||
|
**Tier 2 — Anomalni (Super rare portali):**
|
||||||
|
Dino Valley 🦖, Mythical Highlands 🐉, Endless Forest 🌲, Loch Ness 🦕, Catacombs 💀, Egyptian Desert 🏺, Amazon 🌴, Atlantis 🌊, Chernobyl ☢️, Mexican Cenotes 🇲🇽, Witch Forest 🧙♀️
|
||||||
|
|
||||||
|
**Chernobyl = FINAL ZONE**:
|
||||||
|
- Kai najde Ano v Reactor Core
|
||||||
|
- Boss Fight: Dr. Krnić + Giant Troll King (Phase 1)
|
||||||
|
- **ANA REŠENA** — postane playable character
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💜 AKT 4: REUNIJA & KONEC (Faza 3)
|
||||||
|
|
||||||
|
**THE REUNION:**
|
||||||
|
```
|
||||||
|
Kai enters Ana's cell.
|
||||||
|
Ana turns around.
|
||||||
|
|
||||||
|
ANA: "...Kai?"
|
||||||
|
KAI: "Ana... I found you."
|
||||||
|
[They embrace]
|
||||||
|
ANA: "You came for me..."
|
||||||
|
KAI: "Always. Twin Bond, remember?"
|
||||||
|
|
||||||
|
[Twin Bond FULLY AWAKENS — zlatem svit]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ana se pridruži** — dobita Twin Bond sposobnosti:
|
||||||
|
- Twin Telepathy (komunicirata brez razdalje)
|
||||||
|
- Twin Strike (skupni napad, 2× poškodbe)
|
||||||
|
- Twin Shield (preneseta damage drug na drugega)
|
||||||
|
- Twin Sense (vidita skozi zidove)
|
||||||
|
- Resurrection Ultimate (vsak reši drugega 1×/dan)
|
||||||
|
|
||||||
|
**4 konci (player choice):**
|
||||||
|
```
|
||||||
|
Dr. Krnić (dying): "Edini način zdravila zahteva Alpha Hybrid žrtev."
|
||||||
|
|
||||||
|
[A] Kai se žrtvuje → Ana vodi novi svet, ima hčer "Kai"
|
||||||
|
[B] Ana se žrtvuje → Kai vodi novi svet, ima hčer "Ana"
|
||||||
|
[C] Oba zavrneta → Živita skupaj na kmetiji večno, zombiji ostanejo
|
||||||
|
[D] Alternativa* → Oba preživita! Svet ozdravljan. PERFECT ENDING ✨
|
||||||
|
*Samo z vsemi 50 Aninimi clues + 9 Key Fragmenti
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ GAME SISTEMI (Komplet)
|
||||||
|
|
||||||
|
### 🌱 Farming
|
||||||
|
- **Saditev:** Kai ore tla (E tipka) → posadi semena → zaliva vsak dan → žanje
|
||||||
|
- **Rastline umrejo** brez vode po 1 dnevu
|
||||||
|
- **Demo:** Pšenica (3 dni), Korenje (4 dni)
|
||||||
|
- **Faza 1+:** Konoplja (7 dni, MAIN ECONOMY), Sončnice, 80+ other crops
|
||||||
|
|
||||||
|
### 💧 Voda
|
||||||
|
- **VSA VODA JE STRUPENA** (ocean, reke, luže)
|
||||||
|
- **EDINI VIR:** Deževnica (Rain Catcher)
|
||||||
|
- Brez dežja = kriza → stockpile!
|
||||||
|
- **Faza 1:** Vodni Filter (Oglje+Pesek+Plastika) → mora voda postane pitna
|
||||||
|
|
||||||
|
### 🏠 Housing
|
||||||
|
| Tier | Struktura | Energy restore |
|
||||||
|
|------|-----------|---------------|
|
||||||
|
| 0 | Spalna Vreča | 40% |
|
||||||
|
| 1 | Šotor ⛺ | 70% (Demo MAX) |
|
||||||
|
| 2 | Lesena Koča 🏚️ | 85% (Faza 1 MAX) |
|
||||||
|
| 3 | Kamnita Hiša 🏠 | 95% |
|
||||||
|
| 4 | Moderna Hiša 🏡 | 100% |
|
||||||
|
|
||||||
|
### 🕰️ Dan/Noč
|
||||||
|
- 1 in-game dan = 20 realnih minut
|
||||||
|
- **Noč:** Zombiji 2× močnejši, več spawn-ov
|
||||||
|
- **02:00:** Kai mora spati (ali omedli = energy loss + money loss)
|
||||||
|
|
||||||
|
### 🧟 Zombie Taming
|
||||||
|
- [SPACE] blizu zombija → Alfa Moč → Screen flash → Zombij podrejen
|
||||||
|
- Demo: Max 3 zombiji
|
||||||
|
- Faza 1: Max 8 zombijev
|
||||||
|
- Assign Task: Interact z zombijem → izberi nalogo
|
||||||
|
|
||||||
|
### 🩸 Survival Stats
|
||||||
|
- **HP:** 100 max, regeneracija s hrano
|
||||||
|
- **Energy:** 100%, dreni z delom, regenerira s spanjem/hrano
|
||||||
|
-低 Energy (< 20%): Kai se upočasni
|
||||||
|
|
||||||
|
### 🔧 Crafting
|
||||||
|
- **Campfire:** Osnovno kuhanje
|
||||||
|
- **Workbench (Garaža Faza 1):** Motorka, Laksarca, Filter
|
||||||
|
- **Furnace:** Sand + Coal = Glass, Iron Ore = Iron Bar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 VIZUALNI STIL — PRAVILA ZA GENERIRANJE ASSETOV
|
||||||
|
|
||||||
|
### ABSOLUTNA PRAVILA (nikoli krši):
|
||||||
|
1. **NI PIXEL ART** — High-res ilustracija, smooth lines
|
||||||
|
2. **Thick black outlines** na vseh likih (~3-5px)
|
||||||
|
3. **Gothic Dark Chibi** stil — veliki glave, majhna telesa, ekspresivne oči
|
||||||
|
4. **Muted gothic palette:** Temne modre, vijolične, rjave, sive + neon accenti (teal, zelena)
|
||||||
|
5. **Transparentno ozadje** (Alpha 24 PNG) za vse like in predmete
|
||||||
|
6. **Kai DNA:** Vedno pink/zeleni dreadlocki, rdeče oči, gaugi, nos ring — nikoli drugačen!
|
||||||
|
|
||||||
|
### Skale (relativno na Kai = 64px visok):
|
||||||
|
- Gronk: 2.5× Kai = 160px
|
||||||
|
- Šotor: 2.5× Kai = 160px
|
||||||
|
- Hiša: 3× Kai = 192px
|
||||||
|
- Garaža/Hlev: 4× Kai = 256px
|
||||||
|
- Piščanec: 0.2× Kai = 13px
|
||||||
|
- Kura: 0.5× Kai = 32px
|
||||||
|
- Krava: 2× Kai = 128px
|
||||||
|
|
||||||
|
### Y-Sorting (globina):
|
||||||
|
- Vse se sortira po Y koordinati nog
|
||||||
|
- Kai hodi ZA drevesi ko je višje na ekranu
|
||||||
|
- Kai hodi PRED drevesi ko je nižje na ekranu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 FAZE — KDAJ SE KAJ ZGODI
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEMO │ Dan 1–200 │ Preživi, kmetuj, spoznaj │
|
||||||
|
│ │ │ otok, Twin Bond signali │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ FAZA 1 │ Dan 200+ │ Fog se dvigne, Gronk, │
|
||||||
|
│ (Purchase) │ │ Zombi delavci, Ruševine, │
|
||||||
|
│ │ │ Rudnik, Scout │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ FAZA 2 │ After Faza 1 │ Portali, 20 Biome-ov, │
|
||||||
|
│ │ │ Ana lokacija razjasnjena │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ FAZA 3 │ End Game │ Černobil, Ana rešena, │
|
||||||
|
│ │ │ Twin Bond full power, │
|
||||||
|
│ │ │ Dr. Krnić boss │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ KONEC │ Player Choice │ 4 različni konci │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ TEHNIČNI STACK
|
||||||
|
|
||||||
|
- **Engine:** Phaser 3 (JavaScript)
|
||||||
|
- **Wrapper:** Electron (Desktop app, macOS/Windows/Linux)
|
||||||
|
- **Resolucija:** 1920×1080
|
||||||
|
- **Svet:** 10400×10400px (otok 6400×6400px center)
|
||||||
|
- **Fizika:** Phaser Arcade Physics (brez gravitacije)
|
||||||
|
- **Audio:** Web Audio API (proceduralni) + .ogg za glasbo
|
||||||
|
- **Save:** localStorage (za demo), JSON save file (Faza 1+)
|
||||||
|
|
||||||
|
### Scene struktura:
|
||||||
|
```
|
||||||
|
MenuScene → (IGRAJ gumb) → GrassSceneClean + UIScene (overlay)
|
||||||
|
→ (prihodnost) → IntroScene → GrassSceneClean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 KAR JE ZDAJ V IGRI (2026-03-03)
|
||||||
|
|
||||||
|
| Sistem | Status |
|
||||||
|
|--------|--------|
|
||||||
|
| Phaser 3 + Electron wrapper | ✅ |
|
||||||
|
| Otok (50×50 tilov, jagged obod) | ✅ |
|
||||||
|
| Ocean (animiran, pena) | ✅ |
|
||||||
|
| Kai — hoja vse 4 smeri | ✅ |
|
||||||
|
| Kai — Y-sorting | ✅ |
|
||||||
|
| Drevesa (3 stopnje rasti) | ✅ |
|
||||||
|
| Proceduralni dež (160 črtičnih kapljic) | ✅ |
|
||||||
|
| **Glavni meni (MenuScene)** | ✅ |
|
||||||
|
| **Building System [B]** | ✅ ghost preview + Y-sort + collider |
|
||||||
|
| **UIScene** (HP, energy, hotbar, minimap, inventory) | ✅ |
|
||||||
|
| Farming sistem | ❌ |
|
||||||
|
| Zombi AI | ❌ |
|
||||||
|
| Dan/Noč cikel | ❌ |
|
||||||
|
| Inventar logika | ❌ |
|
||||||
|
| Intro sekvenca (20 polaroidov) | ❌ |
|
||||||
|
| Twin Bond signali | ❌ |
|
||||||
|
| Flashback spomini | ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💬 KLJUČNI DIALOGI (kopiraj direktno)
|
||||||
|
|
||||||
|
### Twin Bond signali (Ana):
|
||||||
|
```
|
||||||
|
"Kai... čutiš me? Sem ujeta... ampak ŽIVIM. Ne obupaj!"
|
||||||
|
"Ne obupaj... Twin Bond... deluje..."
|
||||||
|
"Troll me čuva... ampak nisem sama..."
|
||||||
|
"Kai... najdi me..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gronk:
|
||||||
|
```
|
||||||
|
"Gronk... pomaga šefu?"
|
||||||
|
"Gronk... dober zombi?"
|
||||||
|
"Gronk... sadi... rastline..."
|
||||||
|
"Gronk... šef... žalosten? Gronk... pomagal... najti sestro!"
|
||||||
|
"Gronk... rad... ogenj. Toplo."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Marko (duh):
|
||||||
|
```
|
||||||
|
"Kai... Sin... Utri ograjo."
|
||||||
|
"Skupaj sva verjela v boljši svet. Ne nehaj verjeti."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Elena (duh):
|
||||||
|
```
|
||||||
|
"Rastline potrebujejo vodo, Kai. Kot ti ljubezen."
|
||||||
|
"Nisi kriv. Nikoli nisi bil kriv."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kai (dnevnik):
|
||||||
|
```
|
||||||
|
Dan 1: "Končno... Nov dom. Majhna kmetija, ampak dovolj za začetek."
|
||||||
|
Dan 30: "En mesec. Preživim. Ampak Ana... kje si?"
|
||||||
|
Dan 200: "Dvesto dni. Zdaj se začne pravi boj."
|
||||||
|
```
|
||||||
|
|
||||||
|
### THE REUNION:
|
||||||
|
```
|
||||||
|
Kai: "Ana... I found you."
|
||||||
|
Ana: "...Kai? You came for me..."
|
||||||
|
Kai: "Always. Twin Bond, remember?"
|
||||||
|
Ana: "I felt you. Every day. Searching..."
|
||||||
|
[Twin Bond fully awakens]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*"Twin Bond. Zdaj in vedno."* 💜
|
||||||
|
*© 2026 HipoDevil666 | Nova Farma / Krvava Žetev*
|
||||||
300
nova farma TRAE/dokumentacija/STATUS_DEMO_FAZA1_2026_03_03.md
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
# 🎮 NOVA FARMA — KOMPLET STATUS PLAN
|
||||||
|
## Demo + Faza 1 | Posodobljeno: 2026-03-03
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Ta dokument je **živi status** — tukaj je vse kar je narejeno, kaj manjka, in v kakšnem vrstnem redu se dela.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ BIG PICTURE — Kaj je igra?
|
||||||
|
|
||||||
|
**Nova Farma (Mrtva Dolina)** je survival farming RPG v Phaser 3 / Electron.
|
||||||
|
|
||||||
|
```
|
||||||
|
DEMO → čez 200 dni se odklene → FAZA 1 → Faza 2+ (prihodnost)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Platforma:** Desktop (Electron + Phaser 3)
|
||||||
|
- **Svet:** Otok 50×50 tilov (6400px) v oceanu (10400px)
|
||||||
|
- **Protagonista:** Kai Marković (14 let) | sestra Ana ugrabljena
|
||||||
|
- **Vizualni stil:** 2.5D Hand-Drawn / Dark Chibi Noir (NI pixel art!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ KAJ JE ŽE NAREJENEGA (Engine & World)
|
||||||
|
|
||||||
|
### 🟢 DELUJE ZDAJ (2026-03-03)
|
||||||
|
|
||||||
|
| Sistem | Status | Opomba |
|
||||||
|
|--------|--------|--------|
|
||||||
|
| Electron + Phaser 3 wrapper | ✅ Dela | Stabilno |
|
||||||
|
| Otok (50×50 grid, jagged obod) | ✅ Dela | 6400×6400px |
|
||||||
|
| Ocean (deep blue ozadje) | ✅ Dela | Klif efekt |
|
||||||
|
| Pena ob otoku (foam animation) | ✅ Dela | Animirana |
|
||||||
|
| Kai — hoja vse 4 smeri | ✅ Dela | Walk sheet 16 frames |
|
||||||
|
| Kai — Y-sorting (globina) | ✅ Dela | `setDepth(this.y)` |
|
||||||
|
| Kai — trki z robom otoka | ✅ Dela | Physics bounds |
|
||||||
|
| Zoom (mouse wheel 0.2–5×) | ✅ Dela | |
|
||||||
|
| Tla (grass tile grid) | ✅ Dela | Island mask |
|
||||||
|
| Drevesa (sapling → adult) | ✅ Dela | 3 stopnje rasti |
|
||||||
|
| Proceduralni dež | ✅ Dela | 160 črtičnih kapljic, naraven |
|
||||||
|
| 🆕 Building System [B] | ✅ Dela | Ghost + Y-sort + collider |
|
||||||
|
| Auto-loader vegetacije | ✅ Dela | 28px/160px pixel-perfect |
|
||||||
|
|
||||||
|
### 🟡 DELNO / OKVIRNO
|
||||||
|
|
||||||
|
| Sistem | Status | Kaj manjka |
|
||||||
|
|--------|--------|-----------|
|
||||||
|
| GrassScene — rast trave | 🟡 Partial | Deluje, ampak brez vizualnega feedback-a |
|
||||||
|
| Gronk sprite | 🟡 Asset je | Koda odstranjena (clean start) |
|
||||||
|
| Rain Catcher asset | 🟡 Asset je | Ni integriran v gameplay |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ DEMO — KAJ MANJKA (Prioritetni seznam)
|
||||||
|
|
||||||
|
Demo = **prvo kar bo videl igralec**. Mora vsebovati:
|
||||||
|
|
||||||
|
### 🔴 KRITIČNO — Brez tega Demo ne more iti ven
|
||||||
|
|
||||||
|
#### 1. 🎬 INTRO SEKVENCA (20 Polaroid slik)
|
||||||
|
- [ ] `polaroid_01_druzina.png` — Družina pred virusom
|
||||||
|
- [ ] `polaroid_02_lab.png` — Oče v laboratoriju
|
||||||
|
- [ ] `polaroid_03_izbruh.png` — Red Alert
|
||||||
|
- [ ] `polaroid_04_kaos.png` — Kaos na ulicah
|
||||||
|
- [ ] `polaroid_05_dom.png` — Dom obkoljen
|
||||||
|
- [ ] `polaroid_06_oce.png` — Oče se bori
|
||||||
|
- [ ] `polaroid_07_mama.png` — Mama pada
|
||||||
|
- [ ] `polaroid_08_kai_ugriznjen.png` — Kai ugriznjen
|
||||||
|
- [ ] `polaroid_09_ana.png` — Ana ugrabljena
|
||||||
|
- [ ] `polaroid_10_trol.png` — Veliki Trol Kralj
|
||||||
|
- [ ] `polaroid_11_sam.png` — Sam
|
||||||
|
- [ ] `polaroid_12_alfa.png` — Alfa Moč
|
||||||
|
- [ ] `polaroid_13_zombi.png` — Prvi kontrolirani zombi
|
||||||
|
- [ ] `polaroid_14_pobeg.png` — Pobeg
|
||||||
|
- [ ] `polaroid_15_potovanje.png` — Potovanje
|
||||||
|
- [ ] `polaroid_16_dolina.png` — Najdena dolina
|
||||||
|
- [ ] `polaroid_17_kmetija.png` — Opuščena kmetija
|
||||||
|
- [ ] `polaroid_18_seme.png` — Prvi semenski
|
||||||
|
- [ ] `polaroid_19_bond.png` — Twin Bond
|
||||||
|
- [ ] `polaroid_20_obljuba.png` — Obljuba
|
||||||
|
- [ ] **Koda:** IntroScene.js — prikaz polaroidov s tipografiko
|
||||||
|
|
||||||
|
#### 2. 🌱 FARMING SISTEM
|
||||||
|
- [ ] Kai puede s Hoem (E tipka na tleh)
|
||||||
|
- [ ] `crop_wheat_stage0-3.png` — Pšenica (4 stopnje rasti)
|
||||||
|
- [ ] `crop_carrot_stage0-4.png` — Korenje (5 stopenj rasti)
|
||||||
|
- [ ] Zalivanje (Zalivalka iz inventarja)
|
||||||
|
- [ ] Harvest (klik na zrelo rastlino)
|
||||||
|
- [ ] Rastlina umre brez vode po 1 dnevu
|
||||||
|
|
||||||
|
#### 3. 🧟 ZOMBI SISTEM (Basic)
|
||||||
|
- [ ] `shambler_walk_sheet.png` — Osnova zombi (4 smeri × 4 frame)
|
||||||
|
- [ ] Spawn zombijev (max 3 na otoku)
|
||||||
|
- [ ] AI: Shambler — počasi sledi Kai-u
|
||||||
|
- [ ] SPACE tipka → Alfa Moč → zombi udomačen
|
||||||
|
- [ ] Udomačen zombi "sledi" Kai-u
|
||||||
|
|
||||||
|
#### 4. 🕰️ DAN/NOČ CIKEL
|
||||||
|
- [ ] 20-minutni cikel (1 in-game dan)
|
||||||
|
- [ ] Vizualna sprememba svetlobe (ambient tint)
|
||||||
|
- [ ] Noč: Kai mora v šotor (ali omedli)
|
||||||
|
- [ ] Šotor: Sleep → preskoči na jutro
|
||||||
|
|
||||||
|
#### 5. ❤️ UI / HUD
|
||||||
|
- [ ] Zdravje (5 src, HP bar)
|
||||||
|
- [ ] Energija (Energy bar)
|
||||||
|
- [ ] Dan + Ura (top right)
|
||||||
|
- [ ] Trial Version banner (top left)
|
||||||
|
- [ ] Inventar ozadje (hotbar bottom)
|
||||||
|
- [ ] Inventar popup (I tipka)
|
||||||
|
|
||||||
|
#### 6. 📦 START INVENTAR
|
||||||
|
- [ ] Kai se pojavi z: Kruh×1, Jabolko×1, Bakla×1, Semena pšenica×10, korenje×5
|
||||||
|
- [ ] Začetna skrinja na kmetiji (chest object + open menu)
|
||||||
|
|
||||||
|
#### 7. 🧠 AMNEZIJA SISTEM
|
||||||
|
- [ ] Blur efekt ob startu
|
||||||
|
- [ ] 3 Memory Fragments (blizu predmetov)
|
||||||
|
- [ ] Screen glitch flash → Polaroid spomin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟠 VISOKA PRIORITETA — Demo je bolj popoln z njimi
|
||||||
|
|
||||||
|
#### 8. 🌧️ RAIN CATCHER (Lovilec deževnice)
|
||||||
|
- [ ] Integracija rain_catcher.png v gameplay
|
||||||
|
- [ ] Ko dežuje → zbira vodo
|
||||||
|
- [ ] Voda potrebna za zalivanje in pitje
|
||||||
|
|
||||||
|
#### 9. 🏠 ŠOTOR (Tent) — SLEEP SISTEM
|
||||||
|
- [ ] Klik na šotor → "Spati?" dialog
|
||||||
|
- [ ] Potrditev → Fade to black → jutro (preskoči noč)
|
||||||
|
- [ ] Energy restore: 70%
|
||||||
|
|
||||||
|
#### 10. 💬 DIALOG + TYPEWRITER
|
||||||
|
- [ ] Typewriter efekt za tekst
|
||||||
|
- [ ] První dialog: Gronk (tutorial NPC)
|
||||||
|
- [ ] Twin Bond sporočila od Ane (random 1/dan)
|
||||||
|
|
||||||
|
#### 11. 🎵 ZVOK
|
||||||
|
- [ ] Proceduralni zvok dežja (Web Audio šum)
|
||||||
|
- [ ] Koračaji (`footstep.ogg` ali proceduralni)
|
||||||
|
- [ ] Sekanje dreves
|
||||||
|
- [ ] Ambientna glasba (žuborenje oceana)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔵 FAZA 1 — Odkleni se po 200 dneh
|
||||||
|
|
||||||
|
> Faza 1 se začne na **istem save filu**, samo se odklene vsebina.
|
||||||
|
|
||||||
|
### FAZA 1 CHECKLIST
|
||||||
|
|
||||||
|
#### Svet & Mapa
|
||||||
|
- [ ] **Fog of War** — skrita mapa okrog otoka
|
||||||
|
- [ ] **Fog odpiranje** — Zombi Izvidnik razkrije območja
|
||||||
|
- [ ] **3 Ruševine** v fogu: Garaža, Rastlinjak, Hlev
|
||||||
|
- [ ] **Rudnik** (5×5 area, vhod zrušen)
|
||||||
|
|
||||||
|
#### Zombi Workforce (5 tipov)
|
||||||
|
- [ ] 🕵️ **Izvidnik (Scout)** — Gre v Fog, prinese material, odpira meglo (Max 1)
|
||||||
|
- [ ] 🌾 **Kmet (Farmer)** — Zaliva, žanje (Max 3)
|
||||||
|
- [ ] 🪓 **Gozdar (Lumberjack)** — Seka drevesa (Max 2)
|
||||||
|
- [ ] ⛏️ **Rudar (Miner)** — Koplje rudo, Lv5+ najde železo (Max 2)
|
||||||
|
- [ ] 📦 **Nosač (Porter)** — Pobira Drop Boxe → polaga v skrinje (Max 1)
|
||||||
|
|
||||||
|
#### Drop Box Sistem
|
||||||
|
- [ ] Drop Box objekt (Zombiji odlagajo sem)
|
||||||
|
- [ ] 3 lokacije: ob workshopu, rudniku, farmi
|
||||||
|
- [ ] Kai pobere ročno iz Drop Boxa
|
||||||
|
|
||||||
|
#### Crafting (v Garaži)
|
||||||
|
- [ ] 🪚 **Motorka/Laksarca** (Motor + Veriga + Glava + Bencin)
|
||||||
|
- [ ] 🌿 **Trav-rezalnik** (Motor + Rotating Head + Palica)
|
||||||
|
- [ ] 💧 **Vodni Filter** (Oglje + Pesek + Plastika) — Ocean water → Clean water + Sol
|
||||||
|
- [ ] 🛢️ **Stiskalnica** (Iron×5 + Wood×20 + Stone×10) — Sončnice → Bio-olje
|
||||||
|
|
||||||
|
#### Rudarjenje
|
||||||
|
- [ ] Kop z Motiko (E blizu kamnine → zbira material)
|
||||||
|
- [ ] Baker (Copper) — common
|
||||||
|
- [ ] Premog (Coal) — common
|
||||||
|
- [ ] Glina (Clay) — blizu vode
|
||||||
|
- [ ] Železo (Iron) — samo Rudar Lv5+!
|
||||||
|
|
||||||
|
#### Živali (Nakup)
|
||||||
|
- [ ] Dve živali dostopni v Fazi 1: Kura + Krava
|
||||||
|
- [ ] Delivery sistem (živali pridejo v škatli)
|
||||||
|
- [ ] Kokošnjak (Coop) za Kure
|
||||||
|
- [ ] Hlev (Barn) za Krave + Gronka + Susi
|
||||||
|
|
||||||
|
#### Nove Crops (Faza 1 odkleni)
|
||||||
|
- [ ] Konoplja (Cannabis) — 7 dni, Main Economy
|
||||||
|
- [ ] Sončnice (Sunflowers) → Bio-olje
|
||||||
|
- [ ] Krompir, Paradižnik, Bučke + 80+ more (postopno)
|
||||||
|
|
||||||
|
#### Gronk & Duhovi
|
||||||
|
- [ ] Gronk odkleni v Fazi 1 (Demo: Locked)
|
||||||
|
- Izjema: Prvih 20 kupcev + Streamerji → takoj!
|
||||||
|
- [ ] Susi Jazbečarka (Gronkova psička)
|
||||||
|
- [ ] Oltar → Duh Očeta in Matere
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 REALNI NAPREDEK (2026-03-03)
|
||||||
|
|
||||||
|
```
|
||||||
|
DEMO NAPREDEK:
|
||||||
|
████░░░░░░░░░░░░░░░░░░ ~20%
|
||||||
|
|
||||||
|
Kaj je: Svet, Kai, Drevesa, Building sistem, Dež
|
||||||
|
Kaj ni: Farming, Zombi AI, UI, Intro, Dan/Noč, Inventar
|
||||||
|
|
||||||
|
FAZA 1 NAPREDEK:
|
||||||
|
█░░░░░░░░░░░░░░░░░░░░░ ~5%
|
||||||
|
|
||||||
|
Kaj je: Building mood (temelj za zgradbe)
|
||||||
|
Kaj ni: Vse ostalo
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 PRIPOROČENI VRSTNI RED DELA
|
||||||
|
|
||||||
|
### 📅 SPRINT 1 — "Osnove Preživetja" (~2–3 seje)
|
||||||
|
1. **HUD** — hearts, energy bar, dan/ura text (brez slike, samo tekst ok)
|
||||||
|
2. **Inventar** — Kai dobi start items ob spawnu
|
||||||
|
3. **Šotor** — Sleep interakcija (fade → jutro)
|
||||||
|
4. **Dan/Noč** — ambient tint + ura teče
|
||||||
|
|
||||||
|
### 📅 SPRINT 2 — "Kmetovanje" (~3–4 seje)
|
||||||
|
5. **Tla Farming** — Kai hora s hoem (E tipka)
|
||||||
|
6. **Crop assets** — Wheat (4 stopnje) + Carrot (5 stopenj) generate
|
||||||
|
7. **Planting + Watering** — logika rasti
|
||||||
|
8. **Harvest** — klik na zrelo → inventar
|
||||||
|
|
||||||
|
### 📅 SPRINT 3 — "Zombi" (~2–3 seje)
|
||||||
|
9. **Shambler sprite** — generirati walk sheet
|
||||||
|
10. **Zombi AI** — follow Kai, basic pathfinding
|
||||||
|
11. **Alfa Moč** (SPACE) — udomačitev
|
||||||
|
|
||||||
|
### 📅 SPRINT 4 — "Zgodba" (~2 seje + asset generiranje)
|
||||||
|
12. **20 Polaroid slik** — generirati z AI
|
||||||
|
13. **IntroScene.js** — prikaz polaroidov
|
||||||
|
14. **Amnezija sistem** — blur + 3 spomini
|
||||||
|
|
||||||
|
### 📅 SPRINT 5 — "Demo Polish" (~1–2 seje)
|
||||||
|
15. **Rain Catcher** — gameplay integracija
|
||||||
|
16. **Zvoki** (proceduralni ali AI generated)
|
||||||
|
17. **Trial Version UI** banner
|
||||||
|
|
||||||
|
> Šele po tem → Faza 1 razvoj!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 ASSET STATUS
|
||||||
|
|
||||||
|
### ✅ Imamo
|
||||||
|
- `kai_walk_sheet.png` — 16 frame walk (4 smeri × 4)
|
||||||
|
- `gronk_walk_sheet.png` — asset je, koda disabled
|
||||||
|
- `sotor.png`, `campfire` (taborni_ogenj), `rain_catcher`, `foundation_concrete`
|
||||||
|
- `tree_adult_0-5.png`, `drevo_faza_1/2/veliko.png`
|
||||||
|
- `grass_ref_1`, `visoka_trava_v2`, `grass_cluster_*`
|
||||||
|
- `dead_nature_0-8.png`, `fence_sign_0-2.png`
|
||||||
|
- `rain_drops.png`
|
||||||
|
- UI asseti: hotbar, inventory_panel, health_bar, dialog_panel, itd.
|
||||||
|
|
||||||
|
### ❌ Manjka (DEMO KRITIČNO)
|
||||||
|
- `shambler_walk_sheet.png` (zombi, 4 smeri × 4 frame)
|
||||||
|
- `crop_wheat_stage0-3.png` (4 stopnje rasti)
|
||||||
|
- `crop_carrot_stage0-4.png` (5 stopenj rasti)
|
||||||
|
- `polaroid_01-20.png` (20 polaroidnih slik — biggest work)
|
||||||
|
- `heart_full.png` / `heart_empty.png` (za UI)
|
||||||
|
- `sleeping_bag_interact.png` (animirana verzija)
|
||||||
|
|
||||||
|
### ❌ Manjka (FAZA 1)
|
||||||
|
- `shambler_idle.png`, `shambler_tamed.png`
|
||||||
|
- `zombie_scout_*.png`, `zombie_farmer_*.png`, itd.
|
||||||
|
- `garage_ruin.png`, `greenhouse_ruin.png`, `barn_ruin.png`
|
||||||
|
- `drop_box.png`
|
||||||
|
- `konoplja_stage0-7.png`
|
||||||
|
- Kura, Krave spritei
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 SKUPNI POVZETEK
|
||||||
|
|
||||||
|
| Faza | Napredek | Ostaja |
|
||||||
|
|------|----------|--------|
|
||||||
|
| **Svet/Engine** | 🟢 85% | Samo polish |
|
||||||
|
| **Demo — Gameplay** | 🔴 15% | Farming, AI, Inventar |
|
||||||
|
| **Demo — Vsebina** | 🔴 5% | Intro, dialogi, UI |
|
||||||
|
| **Demo — Assets** | 🟡 30% | Manjka farming + zombi sprites |
|
||||||
|
| **Faza 1** | 🔴 5% | Praktično vse |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Posodobljeno: 2026-03-03 ob 13:04*
|
||||||
|
*Za detajle: `DEMO_FAZA1_COMPLETE.md` | `GAME_BIBLE_FINAL_2026.md`*
|
||||||
@@ -0,0 +1,394 @@
|
|||||||
|
# 📖 NOVA FARMA — KOMPLETNA ZGODBA & IMPLEMENTACIJSKI PLAN
|
||||||
|
## Krvava Žetev | Verzija: 2026-03-03
|
||||||
|
|
||||||
|
> Tukaj je VSE: zgodba, kako se prikaže v igri, v kakšnem vrstnem redu jo gradiva, in kateri assets & koda so potrebni za vsak del.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 SVET & KONTEKST
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-|-|
|
||||||
|
| **Leto** | 2084 |
|
||||||
|
| **Lokacija** | Slovenija → Skriti otok v Dolini |
|
||||||
|
| **Žanr** | Survival Farming RPG + Zombie Control |
|
||||||
|
| **Core Theme** | Preživetje, Iskanje sestre, Obnova civilizacije |
|
||||||
|
| **Visual Style** | 2.5D Dark Chibi Noir |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 LIKI
|
||||||
|
|
||||||
|
| Lik | Starost | Vloga | Status v Demu |
|
||||||
|
|-----|---------|-------|--------------|
|
||||||
|
| **Kai Marković** | 14 | Protagonist, Alpha Hybrid | ✅ Player character |
|
||||||
|
| **Ana Marković** | 14 | Dvojčica, UGRABLJENA | 💜 Samo glas (Twin Bond) |
|
||||||
|
| **Marko Marković** | ~45 | Oče, znanstvenik | 👻 Duh (po oltar questu) |
|
||||||
|
| **Elena Marković** | ~43 | Mama, znanstvenica | 👻 Duh (po oltar questu) |
|
||||||
|
| **Gronk** | ? | Zombi, prvi pomočnik | 🟡 Faza 1 unlock |
|
||||||
|
| **Dr. Krnić** | ~60 | Glavni zlobnik | 🔒 Faza 2+ |
|
||||||
|
| **Giant Troll King** | ? | Ugrabil Ano | 🔒 Faza 2+ |
|
||||||
|
|
||||||
|
### Kai — DNA & Osebnost
|
||||||
|
- **Lasje:** Pink/Zeleni dreadlocki
|
||||||
|
- **Oči:** Rdeče (genetska lastnost Marković linije)
|
||||||
|
- **Outfit:** Raztrgane cunje (start), mogoče upgrade
|
||||||
|
- **Karakter:** Tih, določen, redko govori — ampak ko govori, je globoko
|
||||||
|
- **Posebnost:** Alfa Hybrid — ugriznjen, a ni postal zombi → dobi moč *kontrolirati* zombije
|
||||||
|
|
||||||
|
### Ana — Zakaj je vse drugače brez nje
|
||||||
|
- Kai jo čuti skozi **Twin Bond** (pasivna telepatija)
|
||||||
|
- Random sporočila enkrat na dan (redka, šibka)
|
||||||
|
- Ves Kai-ev gameplay je motiviran z "najti Ano"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎬 ZGODBA — AKT PO AKT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📼 PROLOG (Pred igro — Intro Sekvenca)
|
||||||
|
|
||||||
|
**Prikaže se samo enkrat ob prvem zagonu** — 20 Polaroid slik s typewriter komentarji.
|
||||||
|
|
||||||
|
```
|
||||||
|
[Slika 1] Družina pred virusom → "Pred virusom... bili smo srečni."
|
||||||
|
[Slika 2] Oče v laboratoriju → "Oče je delal na cepilu..."
|
||||||
|
[Slika 3] Red Alert → "Potem je prišel DAN 0..."
|
||||||
|
[Slika 4] Kaos na ulicah → "Svet se je sesula v 48 urah."
|
||||||
|
[Slika 5] Dom obkoljen → "DAN 3: Prišli so k nam..."
|
||||||
|
[Slika 6] Oče se bori → "Oče: 'Kai... varuj sestro... beži...'"
|
||||||
|
[Slika 7] Mama pada → "Mama: 'Ana... Kai... ljubiva vaju...'"
|
||||||
|
[Slika 8] Kai ugriznjen → "Ugriznili so me... ampak nisem postal zombi."
|
||||||
|
[Slika 9] Ana ugrabljena → "Ana: 'KAIII! POMAGAJ!'"
|
||||||
|
[Slika 10] Troll King → "Nekega... POŠASTI jo je odnesel."
|
||||||
|
[Slika 11] Sam → "Starši mrtvi. Sestra ugrabljena. Sam."
|
||||||
|
[Slika 12] Alfa Moč → "Nekaj se je zbudilo v meni..."
|
||||||
|
[Slika 13] Prvi zombi → "Zombiji... me poslušajo?"
|
||||||
|
[Slika 14] Pobeg → "Moral sem pobegniti... preživeti..."
|
||||||
|
[Slika 15] Potovanje → "7 dni pešačenja skozi peklo..."
|
||||||
|
[Slika 16] Dolina → "Našel sem jo. Mojo dolino."
|
||||||
|
[Slika 17] Kmetija → "Majhna kmetija. Nov začetek."
|
||||||
|
[Slika 18] Semena → "Če bom preživel... jo bom našel."
|
||||||
|
[Slika 19] Twin Bond → "Čutim jo... Ana je ŽIVA."
|
||||||
|
[Slika 20] Obljuba → "Prisegli sem... Najti jo bom. Ne glede na ceno."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Po polaroidih:**
|
||||||
|
- Fade to black
|
||||||
|
- Kai se prebudi v **Spalni vreči** ← prvi frame igre
|
||||||
|
- Vid zamegljen (amnezija blur efekt)
|
||||||
|
- Zveni šibak signal: *"Kai... sem tu..."* (Ana)
|
||||||
|
- Blur se razjasni → **IGRA SE ZAČNE**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏝️ AKT 1: PREŽIVETJE (Demo — Dan 1–200)
|
||||||
|
|
||||||
|
**Cilj:** Preživi 200 dni → odkleni Fazo 1
|
||||||
|
|
||||||
|
**Kaj se dogaja:**
|
||||||
|
Kai sam na otoku. Gradi, kmetuje, zbira material. Vsak dan je boj.
|
||||||
|
Voda je samo iz dežja (Rain Catcher). Zombiji napadajo ponoči.
|
||||||
|
|
||||||
|
#### Zgodba teče skozi 3 kanale:
|
||||||
|
|
||||||
|
**1. DNEVNIK (Kai-ev notranji monolog)**
|
||||||
|
Vsak dan ob prvem pritisku na Sleep:
|
||||||
|
```
|
||||||
|
DAN 1: "Končno... Nov dom. Majhna kmetija, ampak dovolj za začetek."
|
||||||
|
DAN 2: "Danes sem srečal zombija. Ime sem mu dal Gronk. (Faza 1 unlock)"
|
||||||
|
DAN 3: "Flashback... Vidim mamo, očeta, Ano... Boli. Ampak moram naprej."
|
||||||
|
DAN 7: "Teden dni sam. Twin Bond signal je bil šibak nocoj."
|
||||||
|
DAN 30: "En mesec. Preživim. Kmetija raste. Ampak Ana... KJE SI?"
|
||||||
|
DAN 100:"Sto dni. Pol poti. Zbral sem dovolj za... Moram naprej."
|
||||||
|
DAN 200:"Dvesto dni. Naredil sem, kar sem si obljubil. Zdaj se začne pravi boj."
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. TWIN BOND SIGNALI** (naključno 1× na dan, ~10% chance)
|
||||||
|
```
|
||||||
|
[Šibek telepatski glas]
|
||||||
|
Ana: "Kai... čutiš me? Sem ujeta... ampak ŽIVIM. Ne obupaj!"
|
||||||
|
Ana: "Ne obupaj... Twin Bond... deluje..."
|
||||||
|
Ana: "Troll me čuva... ampak nisem sama..."
|
||||||
|
Ana: "Kai... najdi me..."
|
||||||
|
[Signal izgubljen]
|
||||||
|
```
|
||||||
|
Visual: Vijolični blisk na zaslonu, Kai zamrzne za sekundo, nato nadaljuje.
|
||||||
|
|
||||||
|
**3. FLASHBACK SPOMINI** (trigger: pristopi k posebnim predmetom)
|
||||||
|
|
||||||
|
| Predmet | Spomin |
|
||||||
|
|---------|--------|
|
||||||
|
| Star foto na tleh | "Srečni Časi" — Ana z metuljem v roki |
|
||||||
|
| Testna epruveta | "Očetov Laboratorij" — "Nikoli ne dotikaj teh vzorčkov" |
|
||||||
|
| Stara sveča | "Zadnja Večerja" — Anin rojstni dan, torta, smeh |
|
||||||
|
|
||||||
|
Vsak flashback = blago screen blur → Polaroid naredi "klik" → glas → nazaj.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌫️ AKT 2: FAZA 1 — ODPRTJE (Dan 200+)
|
||||||
|
|
||||||
|
**Trigger:** 200 dni preživetih → fog of war se začne odpirati
|
||||||
|
|
||||||
|
**Unlock:** Gronk (ali prvih 20 kupcev ga dobijo takoj)
|
||||||
|
|
||||||
|
```
|
||||||
|
[Fog se delno dvigne]
|
||||||
|
SISTEM: "Megla se dviga... Svet je večji kot si mislil."
|
||||||
|
|
||||||
|
KAI (misli): "Kaj je tam zunaj...?"
|
||||||
|
|
||||||
|
[Gronk se pojavi]
|
||||||
|
GRONK: "Gronk... pomaga šefu?"
|
||||||
|
KAI: "Ti si... Gronk. Moj pomočnik."
|
||||||
|
GRONK: "Gronk... dober zombi?"
|
||||||
|
KAI: "Najboljši zombi."
|
||||||
|
GRONK: *Srečno mrmra*
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nove zgodbe:**
|
||||||
|
- Scout Zombi razkrije ruševine v fogu
|
||||||
|
- Garaža, Rastlinjak, Hlev — vsaka ruševina ima **izgubo/zgodovino** (napisi v ruinah)
|
||||||
|
- Garaža: "Tukaj je imel nekdo sanje... Popravi jih."
|
||||||
|
- Rudnik: "Globoko pod tlemi leži odgovor..."
|
||||||
|
|
||||||
|
**Gronk Dialogi** (sproži se med delom):
|
||||||
|
```
|
||||||
|
[Med kmetovanjem]
|
||||||
|
GRONK: "Gronk... sadi... rastline..."
|
||||||
|
KAI: "Dobro delo, Gronk!"
|
||||||
|
GRONK: "Gronk... je... ponosen!"
|
||||||
|
|
||||||
|
[Kai je žalosten]
|
||||||
|
GRONK: "Šef... žalosten?"
|
||||||
|
KAI: "Moja sestra... Pogrešam jo..."
|
||||||
|
GRONK: "Gronk... pomagal... najti sestro!"
|
||||||
|
KAI: "Hvala, Gronk. Res si dober prijatelj."
|
||||||
|
|
||||||
|
[Ko sreča Kai ob ognju zvečer]
|
||||||
|
GRONK: "Gronk... rad... ogenj. Toplo."
|
||||||
|
KAI: "Jaz pa rad... mislim na Ano."
|
||||||
|
GRONK: "Ana... dobra? Kot šef?"
|
||||||
|
KAI: "Boljša. Ona je genialna."
|
||||||
|
|
||||||
|
[Grozno vreme]
|
||||||
|
GRONK: "Gronk... ne mara... mokro."
|
||||||
|
KAI: "Nobeden ne mara dežja, Gronk."
|
||||||
|
GRONK: "Gronk... ljubi... Gronk lažje govori to."
|
||||||
|
KAI: ["..."smeh]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parental Ghost (Oltar event):**
|
||||||
|
Kai zgradi oltar (20 Kamen + 10 Les + 2 Sveči) in najde predmete:
|
||||||
|
- **Inženirska ura** (Markov predmet) → Marko se pojavi kot duh
|
||||||
|
- **Medaljon** (Elenin predmet) → Elena se pojavi kot duh
|
||||||
|
|
||||||
|
```
|
||||||
|
[Marko Duh — moder svit]
|
||||||
|
MARKO: "Kai... Sin..."
|
||||||
|
KAI: "Ata? Ali si... res ti?"
|
||||||
|
MARKO: "Del mene ostaja. Dokler se spomniš."
|
||||||
|
MARKO: "Utri ograjo, sin. Zombiji so šibki, ampak horda..."
|
||||||
|
BUFF AKTIVEN: "Očetova Modrost: +15% Craft hitrost"
|
||||||
|
|
||||||
|
[Elena Duh — zlat svit]
|
||||||
|
ELENA: "Moj Kai... kako si zrasel."
|
||||||
|
KAI: "Mama... Žal mi je. Nisem mogel—"
|
||||||
|
ELENA: "Molči. Nisi kriv. Nikoli nisi bil kriv."
|
||||||
|
ELENA: "Rastline potrebujejo vodo, Kai. Kot ti ljubezen."
|
||||||
|
BUFF AKTIVEN: "Mamina Ljubezen: +10% Energy regen na kmetiji"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💜 AKT 3: REUNIJA (Faza 2+ — prihodnost)
|
||||||
|
|
||||||
|
**Lokacija:** Černobil Reaktor (Level 61–75)
|
||||||
|
|
||||||
|
Kai pride skozi radiation zones, premaga Giant Troll King (Phase 1)...
|
||||||
|
|
||||||
|
```
|
||||||
|
[Kai vstopi v celico]
|
||||||
|
[Ana se obrne]
|
||||||
|
[Njune oči se srečajo]
|
||||||
|
|
||||||
|
ANA: "...Kai?"
|
||||||
|
KAI: "Ana... I found you."
|
||||||
|
|
||||||
|
[Tečeta drug k drugemu, objameta se, jočeta]
|
||||||
|
|
||||||
|
ANA: "You... you came for me..."
|
||||||
|
KAI: "Always. Twin Bond, remember?"
|
||||||
|
ANA: "I felt you. Every day. Searching..."
|
||||||
|
KAI: "Every single day."
|
||||||
|
|
||||||
|
[Twin Bond FULLY AWAKENS — zlata svetloba]
|
||||||
|
```
|
||||||
|
|
||||||
|
**ANA SE PRIDRUŽI** — postane playable character. Unlock vseh Twin Bond abilityjev:
|
||||||
|
- Twin Telepathy (govorita brez distance)
|
||||||
|
- Twin Strike (skupni napad, 2× damage)
|
||||||
|
- Twin Shield (prenos škode med njima)
|
||||||
|
- Twin Sense (vidita skozi zidove)
|
||||||
|
- Shared Buffs (hrana/potions za oba)
|
||||||
|
- Resurrection Ultimate (vsak reši drugega 1×/dan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚖️ AKT 4: ZADNJA IZBIRA (Faza 3 — End Game)
|
||||||
|
|
||||||
|
Dr. Krnić: *"Pridi v Reactor Core. Ali spusti SUPER VIRUS. Imaš 7 dni."*
|
||||||
|
|
||||||
|
Po Boss Rush (Dr. Krnić + Giant Troll King Phase 2):
|
||||||
|
|
||||||
|
```
|
||||||
|
[Cure Machine]
|
||||||
|
Dr. Krnić (dying): "Edini način... Alpha Hybrid Sacrifice.
|
||||||
|
Eden od vaju mora umreti... da se svet pozdravili."
|
||||||
|
|
||||||
|
[Twin Bond: "Skupaj... Premagava?"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**CHOICE SCREEN:**
|
||||||
|
```
|
||||||
|
[ A ] Kai se žrtvuje → Ending 1
|
||||||
|
[ B ] Ana se žrtvuje → Ending 2
|
||||||
|
[ C ] Zavrni → Ending 3 (Dark World)
|
||||||
|
[ D ] Alternativa* → Ending 4 PERFECT ✨
|
||||||
|
* Samo če si zbral VSEH 50 Aninih clues + 9 Key Fragmentov
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏁 4 KONCI
|
||||||
|
|
||||||
|
### Ending 1 — 💔 Kai se žrtvuje
|
||||||
|
Kai: *"Ana... ti si genialna. Svet te potrebuje bolj. Živi. Za naju oba."*
|
||||||
|
Ana: Vodi novi svet. Ima hčer po imenu **Kai**.
|
||||||
|
|
||||||
|
### Ending 2 — 💔 Ana se žrtvuje
|
||||||
|
Ana: *"Kai... ti si rešil mene. Pusti meni rešiti ostale."*
|
||||||
|
Kai: Vodi novi svet. Ima hčer po imenu **Ana**.
|
||||||
|
|
||||||
|
### Ending 3 — 💀 Dark World
|
||||||
|
Oba zavrneta. Zombiji ostanejo. Živita skupaj na kmetiji večno.
|
||||||
|
*"Ne žalim ničesar."* — Kai (68)
|
||||||
|
|
||||||
|
### Ending 4 — 🌟 Perfect ✨
|
||||||
|
Ana: *"Počakaj! Twin Bond energija + 50 clues + 9 Fragmentov = ALTERNATIVNA REŠITEV!"*
|
||||||
|
Oba preživita. Svet ozdravljen. Zombiji postanejo *prijazni delavci*.
|
||||||
|
Zadnja scena: Piknik na kmetiji. Kai, Ana, otroci, smeh.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ KAKO SE ZGODBA GRADI V IGRI
|
||||||
|
|
||||||
|
### Sistem 1: IntroScene.js
|
||||||
|
```
|
||||||
|
ASSETS: polaroid_01.png – polaroid_20.png
|
||||||
|
CODE: IntroScene.js
|
||||||
|
TRIGGER: Samo 1x, ob prvem startu
|
||||||
|
TRACKING: localStorage.setItem('introSeen', true)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sistem 2: Journal (Dnevnik)
|
||||||
|
```
|
||||||
|
ASSETS: ui_dialog.png (már imamo!)
|
||||||
|
CODE: JournalSystem.js (ustvari)
|
||||||
|
TRIGGER: Vsak večer ob kliku Sleep
|
||||||
|
DATA: Array dialogov indexiran po dnevu
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sistem 3: Twin Bond Signal
|
||||||
|
```
|
||||||
|
ASSETS: vijolično overlay (Graphics — brez asseta)
|
||||||
|
CODE: TwinBondSystem.js
|
||||||
|
TRIGGER: Vsak dan, 10% chance ob polnoči
|
||||||
|
SOUND: Ana glas (Edge-TTS proceduralan)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sistem 4: Flashback Memories
|
||||||
|
```
|
||||||
|
ASSETS: 3 predmeti: foto, epruveta, sveča
|
||||||
|
CODE: MemorySystem.js
|
||||||
|
TRIGGER: Kai stopi v 80px bližino predmeta
|
||||||
|
EFFECT: Screen blur → Polaroid click → glas → blur ven
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sistem 5: Gronk Dialogi (Faza 1)
|
||||||
|
```
|
||||||
|
ASSETS: gronk_walk_sheet.png (že imamo!)
|
||||||
|
CODE: GronjSystem.js (dodamo k NPC logic)
|
||||||
|
TRIGGER: Triggeri: ob kmetovanju, po 20+ minutah, ob ognju, jokanju
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sistem 6: Parental Ghosts (Oltar)
|
||||||
|
```
|
||||||
|
ASSETS: oltar_obj.png, inzenirska_ura.png, medaljon.png
|
||||||
|
CODE: AltarSystem.js
|
||||||
|
TRIGGER: Zgradi oltar + najdi oba predmeta
|
||||||
|
EFFECT: Duh se pojavi, dá buff, ostane permanent
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 IMPLEMENTACIJSKI VRSTNI RED
|
||||||
|
|
||||||
|
### 🟢 SPRINT A — Prolog (Intro Sekvenca)
|
||||||
|
```
|
||||||
|
1. Generiramo 20 Polaroid slik (AI)
|
||||||
|
2. Napišemo IntroScene.js
|
||||||
|
- Fade in/out med slikami
|
||||||
|
- Typewriter tekst na vsaki
|
||||||
|
- ESC/Space skip za vsako
|
||||||
|
3. Integriramo v game.js:
|
||||||
|
scene: [IntroScene, GrassScene, UIScene]
|
||||||
|
4. localStorage tracking → ne ponavlja se
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🟡 SPRINT B — Daily Journal
|
||||||
|
```
|
||||||
|
1. Napišemo dialogArray.js (vse Kai dnevniške vnose po dnevu)
|
||||||
|
2. JournalSystem.js → ob kliku "Sleep" prikaže dialog
|
||||||
|
3. UIScene: dialog box za prikaz (dialog_panel.png — že imamo!)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🟠 SPRINT C — Twin Bond
|
||||||
|
```
|
||||||
|
1. TwinBondSystem.js
|
||||||
|
- Vsak dan, 10% chance Signal
|
||||||
|
- Ana voice line (typewriter)
|
||||||
|
- Vijolični blisk efekt
|
||||||
|
- UI: Vijolična ikona v HUD blisne
|
||||||
|
2. HUD: Dodamo Twin Bond ikono (majhna vijolična srce ikona)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔵 SPRINT D — Flashback Memories
|
||||||
|
```
|
||||||
|
1. Postavimo 3 predmete na otok (foto, epruveta, sveča)
|
||||||
|
2. MemorySystem.js — proximity trigger (80px)
|
||||||
|
3. Efekt: Phaser camera.setPostPipeline blur
|
||||||
|
4. Polaroid click zvok (proceduralen)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 ZGODBA STATUS (2026-03-03)
|
||||||
|
|
||||||
|
```
|
||||||
|
PROLOG (20 polaroidov): ██░░░░░░░░ 10% (napisi OK, slike manjkajo)
|
||||||
|
KAI DNEVNIK: ████░░░░░░ 40% (tekst pisan, kode ni)
|
||||||
|
TWIN BOND SIGNALI: █░░░░░░░░░ 5% (dizajn ok, implementacija 0)
|
||||||
|
FLASHBACK MEMORIES: █░░░░░░░░░ 5% (dizajn ok, predmeti manjkajo)
|
||||||
|
GRONK DIALOGI: ████░░░░░░ 40% (tekst pisan, Gronk ni v igri)
|
||||||
|
PARENTAL GHOSTS: █░░░░░░░░░ 5% (samo dizajn)
|
||||||
|
AKT 2-4 (Faza 1+): ░░░░░░░░░░ 0% (prihodnost)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Posodobljeno: 2026-03-03 ob 13:19*
|
||||||
|
*Za dialoge: `DEMO_FAZA1_NAPISI_DIALOGI.md`*
|
||||||
|
*Za full lore: `ZGODBA_CELOTNA.md`*
|
||||||
@@ -3,6 +3,7 @@ if (typeof process !== 'undefined' && process.env) {
|
|||||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import MenuScene from './scenes/MenuScene.js';
|
||||||
import GrassScene from './scenes/GrassScene_Clean.js';
|
import GrassScene from './scenes/GrassScene_Clean.js';
|
||||||
import UIScene from './scenes/UIScene.js';
|
import UIScene from './scenes/UIScene.js';
|
||||||
|
|
||||||
@@ -14,8 +15,8 @@ const config = {
|
|||||||
mode: Phaser.Scale.FIT,
|
mode: Phaser.Scale.FIT,
|
||||||
autoCenter: Phaser.Scale.CENTER_BOTH
|
autoCenter: Phaser.Scale.CENTER_BOTH
|
||||||
},
|
},
|
||||||
pixelArt: true, // Zagotovi ostrino pri zumiranju (pixel art style)
|
pixelArt: false, // Meni uporablja smooth slike
|
||||||
backgroundColor: '#1a1a1a', // Temno siva, da slika izstopa
|
backgroundColor: '#000000',
|
||||||
parent: 'body',
|
parent: 'body',
|
||||||
physics: {
|
physics: {
|
||||||
default: 'arcade',
|
default: 'arcade',
|
||||||
@@ -24,7 +25,9 @@ const config = {
|
|||||||
gravity: { y: 0 }
|
gravity: { y: 0 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scene: [GrassScene, UIScene]
|
// Vrstni red: MenuScene je prva (avtomatski start), ostale so pasivne
|
||||||
|
scene: [MenuScene, GrassScene, UIScene]
|
||||||
};
|
};
|
||||||
|
|
||||||
const game = new Phaser.Game(config);
|
const game = new Phaser.Game(config);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import BuildingSystem from '../systems/BuildingSystem.js';
|
import BuildingSystem from '../systems/BuildingSystem.js';
|
||||||
|
import FarmingSystem from '../systems/FarmingSystem.js';
|
||||||
|
import WaterSystem from '../systems/WaterSystem.js';
|
||||||
|
import DayNightSystem from '../systems/DayNightSystem.js';
|
||||||
|
import InventorySystem from '../systems/InventorySystem.js';
|
||||||
|
import ZombieSystem from '../systems/ZombieSystem.js';
|
||||||
|
|
||||||
export default class GrassSceneClean extends Phaser.Scene {
|
export default class GrassSceneClean extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -123,6 +128,15 @@ export default class GrassSceneClean extends Phaser.Scene {
|
|||||||
|
|
||||||
// 8. Weather System
|
// 8. Weather System
|
||||||
this.load.image('rain_drops', 'DEMO_FAZA1/Environment/rain_drops.png');
|
this.load.image('rain_drops', 'DEMO_FAZA1/Environment/rain_drops.png');
|
||||||
|
|
||||||
|
// 9. Farming System Assets
|
||||||
|
FarmingSystem.preload(this);
|
||||||
|
|
||||||
|
// 10. Water System Assets
|
||||||
|
WaterSystem.preload(this);
|
||||||
|
|
||||||
|
// 11. Zombie System Assets
|
||||||
|
ZombieSystem.preload(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@@ -1091,6 +1105,10 @@ export default class GrassSceneClean extends Phaser.Scene {
|
|||||||
|
|
||||||
console.log('🏗️ Building System initialized — press [B] to open build mode');
|
console.log('🏗️ Building System initialized — press [B] to open build mode');
|
||||||
|
|
||||||
|
// === ZAŽENIMO UIScene kot overlay (HUD sloj) ===
|
||||||
|
if (!this.scene.isActive('UIScene')) {
|
||||||
|
this.scene.launch('UIScene');
|
||||||
|
}
|
||||||
|
|
||||||
// --- ANIMATIONS ---
|
// --- ANIMATIONS ---
|
||||||
// 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up
|
// 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up
|
||||||
@@ -1212,6 +1230,147 @@ export default class GrassSceneClean extends Phaser.Scene {
|
|||||||
|
|
||||||
console.log('🌧️ Rain system initialized (procedural canvas drops)!');
|
console.log('🌧️ Rain system initialized (procedural canvas drops)!');
|
||||||
|
|
||||||
|
|
||||||
|
// ─── FARMING SYSTEM ────────────────────────────────────────────
|
||||||
|
this.farmingSystem = new FarmingSystem(this, {
|
||||||
|
islandX: this.islandX,
|
||||||
|
islandY: this.islandY,
|
||||||
|
tileSize: this.TILE_SIZE,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Harvest event → pokaži gold gain in posodobi inventory
|
||||||
|
this.events.on('harvest', (data) => {
|
||||||
|
console.log(`🌾 Harvest: ${data.item} ×${data.count} = ${data.value}g`);
|
||||||
|
// Posodobi inventory gold (ko bo InventorySystem)
|
||||||
|
if (this.playerGold !== undefined) this.playerGold += data.value;
|
||||||
|
else this.playerGold = data.value;
|
||||||
|
// Emit do UIScene
|
||||||
|
const uiScene = this.scene.get('UIScene');
|
||||||
|
if (uiScene && uiScene.events) {
|
||||||
|
uiScene.events.emit('goldChanged', this.playerGold);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ FarmingSystem ready — [E] ore, posadi, zalij, pozeni!');
|
||||||
|
|
||||||
|
// ─── WATER SYSTEM ────────────────────────────────────────
|
||||||
|
this.waterSystem = new WaterSystem(this, {
|
||||||
|
tileSize: this.TILE_SIZE,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Postavi Rain Catcher blizu centra otoka (kamor Kai spawn-a)
|
||||||
|
const rcX = this.islandX + this.islandWidth / 2 + 200;
|
||||||
|
const rcY = this.islandY + this.islandHeight / 2;
|
||||||
|
this.waterSystem.placeRainCatcher(rcX, rcY);
|
||||||
|
|
||||||
|
// Poveži water system z farming sistemom (preverj vodo pred zalivanjem)
|
||||||
|
this.farmingSystem.waterSystem = this.waterSystem;
|
||||||
|
|
||||||
|
// WATER CHANGED EVENT → UI update
|
||||||
|
this.events.on('waterChanged', (data) => {
|
||||||
|
const uiScene = this.scene.get('UIScene');
|
||||||
|
if (uiScene && uiScene.events) {
|
||||||
|
uiScene.events.emit('waterChanged', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ WaterSystem ready — [F] napolni vedro pri Rain Catcher-ju!');
|
||||||
|
|
||||||
|
// ─── DAY/NIGHT SYSTEM ─────────────────────────────────
|
||||||
|
this.dayNightSystem = new DayNightSystem(this, {
|
||||||
|
speed: 1, // 1 sec real = 1 min igre (24 min = 1 dan)
|
||||||
|
});
|
||||||
|
|
||||||
|
// === EVENT WIRE-UP ===
|
||||||
|
|
||||||
|
// Dan se je spremenil → farma tick
|
||||||
|
this.events.on('dayChanged', ({ day }) => {
|
||||||
|
if (this.farmingSystem) this.farmingSystem.onNewDay(day);
|
||||||
|
console.log(`📅 Dan ${day} prišel!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dež → Rain Catcher zbira
|
||||||
|
this.events.on('rainDay', ({ day }) => {
|
||||||
|
if (this.waterSystem) this.waterSystem.onRain();
|
||||||
|
this.triggerRainfall(); // Vizualni dež
|
||||||
|
console.log(`🌧️ Dežen dan ${day}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Jutro → Rast trave/dreves
|
||||||
|
this.events.on('morningArrived', () => {
|
||||||
|
this.morningGrowthCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ur a sprememba → UIScene clock
|
||||||
|
this.events.on('timeChanged', (data) => {
|
||||||
|
const uiScene = this.scene.get('UIScene');
|
||||||
|
if (uiScene && uiScene.events) {
|
||||||
|
uiScene.events.emit('timeChanged', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Registriraj spalno vrečo kot sleep objekt
|
||||||
|
// (spawn point je center otoka)
|
||||||
|
const sleepX = this.islandX + this.islandWidth / 2;
|
||||||
|
const sleepY = this.islandY + this.islandHeight / 2;
|
||||||
|
this.dayNightSystem.registerSleepObject(sleepX, sleepY, null, 'sleeping_bag');
|
||||||
|
|
||||||
|
console.log('✅ DayNightSystem ready — [Z] za spanje, 1 min/s!');
|
||||||
|
|
||||||
|
// ─── INVENTORY SYSTEM ─────────────────────────────────
|
||||||
|
this.inventorySystem = new InventorySystem(this);
|
||||||
|
|
||||||
|
// Pošlji začetni hotbar stanje v UIScene
|
||||||
|
this.scene.get('UIScene')?.events.emit('hotbarUpdate', {
|
||||||
|
slots: this.inventorySystem.hotbar.map((key, i) => {
|
||||||
|
const def = this.inventorySystem.ITEM_DEFS[key];
|
||||||
|
return key ? {
|
||||||
|
key, icon: def?.icon, name: def?.name,
|
||||||
|
count: this.inventorySystem.getCount(key),
|
||||||
|
active: i === 0, type: def?.type,
|
||||||
|
stackable: def?.stackable
|
||||||
|
} : null;
|
||||||
|
}),
|
||||||
|
activeSlot: 0,
|
||||||
|
gold: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ InventorySystem ready — [1-7] hotbar, scroll wheel!');
|
||||||
|
|
||||||
|
// ─── ZOMBIE SYSTEM ─────────────────────────────────
|
||||||
|
this.zombieSystem = new ZombieSystem(this, {
|
||||||
|
islandX: this.islandX,
|
||||||
|
islandY: this.islandY,
|
||||||
|
islandW: this.islandWidth,
|
||||||
|
islandH: this.islandHeight,
|
||||||
|
});
|
||||||
|
this.zombieSystem.createAnimations();
|
||||||
|
|
||||||
|
// Noč → Začni spawnat
|
||||||
|
this.events.on('nightArrived', () => {
|
||||||
|
this.zombieSystem.startNightSpawning();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Jutro → Ustavi spawn (zombiji ostanejo)
|
||||||
|
this.events.on('morningArrived', () => {
|
||||||
|
this.zombieSystem.stopNightSpawning();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zombi udomačen event
|
||||||
|
this.events.on('zombieTamed', ({ zombie }) => {
|
||||||
|
console.log('✨ Kai je udomačil zombija!');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ ZombieSystem ready — [SPACE] za Alfa Moč!');
|
||||||
|
|
||||||
|
// === TAKOJ SPAWNI ZOMBIJA (za testiranje, podnevi+ponoči) ===
|
||||||
|
this.time.delayedCall(3000, () => {
|
||||||
|
if (this.zombieSystem) {
|
||||||
|
this.zombieSystem.spawnZombie(); // 1x takoj ob startu
|
||||||
|
console.log('🧟 Test zombi spawnan!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1703,6 +1862,26 @@ export default class GrassSceneClean extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === WATER SYSTEM UPDATE ===
|
||||||
|
if (this.waterSystem) {
|
||||||
|
this.waterSystem.update(this.kai);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DAY/NIGHT SYSTEM UPDATE ===
|
||||||
|
if (this.dayNightSystem) {
|
||||||
|
this.dayNightSystem.update(delta, this.kai);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ZOMBIE SYSTEM UPDATE ===
|
||||||
|
if (this.zombieSystem) {
|
||||||
|
this.zombieSystem.update(this.kai, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === FARMING SYSTEM UPDATE ===
|
||||||
|
if (this.farmingSystem) {
|
||||||
|
this.farmingSystem.update(this.kai, time);
|
||||||
|
}
|
||||||
|
|
||||||
// === BUILDING SYSTEM: Ghost follows pointer, building Y-sorting ===
|
// === BUILDING SYSTEM: Ghost follows pointer, building Y-sorting ===
|
||||||
if (this.buildingSystem) {
|
if (this.buildingSystem) {
|
||||||
this.buildingSystem.update(this.input.activePointer);
|
this.buildingSystem.update(this.input.activePointer);
|
||||||
|
|||||||
359
nova farma TRAE/src/scenes/MenuScene.js
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* MenuScene.js — Nova Farma | Glavni Meni
|
||||||
|
* ============================================================
|
||||||
|
* Vsebuje:
|
||||||
|
* - Animirano ozadje (otok ponoči)
|
||||||
|
* - Naslov igre z glow efektom
|
||||||
|
* - Kai lik (lebdi + diha)
|
||||||
|
* - Gumbi: IGRAJ, NASTAVITVE, IZHOD
|
||||||
|
* - Trial Version badge + Purchase badge
|
||||||
|
* - Firefly particle efekt
|
||||||
|
* - Fog (megla) ki se premika
|
||||||
|
* ============================================================
|
||||||
|
*/
|
||||||
|
export default class MenuScene extends Phaser.Scene {
|
||||||
|
constructor() {
|
||||||
|
super({ key: 'MenuScene' });
|
||||||
|
}
|
||||||
|
|
||||||
|
preload() {
|
||||||
|
this.load.path = 'assets/';
|
||||||
|
|
||||||
|
// Ozadje
|
||||||
|
this.load.image('menu_bg', 'DEMO_FAZA1/UI/menu_background.png');
|
||||||
|
|
||||||
|
// Liki
|
||||||
|
this.load.image('kai_menu', 'DEMO_FAZA1/Characters/Kai_Dreads.png');
|
||||||
|
|
||||||
|
// UI Badges
|
||||||
|
this.load.image('badge_trial', 'DEMO_FAZA1/UI/badge_trial.png');
|
||||||
|
this.load.image('badge_buy', 'DEMO_FAZA1/UI/badge_purchase.png');
|
||||||
|
|
||||||
|
// Gumb ozadje (dialog panel za gumbe)
|
||||||
|
this.load.image('ui_dialog', 'DEMO_FAZA1/UI/dialog_panel.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
const W = this.cameras.main.width; // 1920
|
||||||
|
const H = this.cameras.main.height; // 1080
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// OZADJE
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.bg = this.add.image(W / 2, H / 2, 'menu_bg')
|
||||||
|
.setDisplaySize(W, H)
|
||||||
|
.setDepth(0);
|
||||||
|
|
||||||
|
// Temna vinjeta (robovi temnejši)
|
||||||
|
const vignette = this.add.graphics().setDepth(1);
|
||||||
|
const vigRad = this.add.graphics().setDepth(1);
|
||||||
|
// Gradientni overlay — temno rob, svetel center
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const alpha = 0.04 * (8 - i);
|
||||||
|
vignette.fillStyle(0x000000, alpha);
|
||||||
|
vignette.fillRect(0, 0, W * (i * 0.06), H);
|
||||||
|
vignette.fillRect(W - W * (i * 0.06), 0, W * (i * 0.06), H);
|
||||||
|
vignette.fillRect(0, 0, W, H * (i * 0.04));
|
||||||
|
vignette.fillRect(0, H - H * (i * 0.04), W, H * (i * 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// FIREFLIES — lebdeče svetlobne pike
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.fireflies = [];
|
||||||
|
for (let i = 0; i < 35; i++) {
|
||||||
|
const ff = {
|
||||||
|
x: Phaser.Math.Between(60, W - 60),
|
||||||
|
y: Phaser.Math.Between(100, H - 150),
|
||||||
|
r: Phaser.Math.FloatBetween(2, 5),
|
||||||
|
alpha: Phaser.Math.FloatBetween(0.2, 0.9),
|
||||||
|
speed: Phaser.Math.FloatBetween(0.3, 0.9),
|
||||||
|
offset: Math.random() * Math.PI * 2,
|
||||||
|
color: Phaser.Math.RND.pick([0x44ffcc, 0xaaffaa, 0x88ffdd, 0xccff66]),
|
||||||
|
};
|
||||||
|
this.fireflies.push(ff);
|
||||||
|
}
|
||||||
|
this.fireflyGfx = this.add.graphics().setDepth(2);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// KAI — desno, lebdeč
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.kaiImg = this.add.image(W * 0.78, H * 0.62, 'kai_menu')
|
||||||
|
.setScale(0.95)
|
||||||
|
.setOrigin(0.5, 1.0)
|
||||||
|
.setDepth(10);
|
||||||
|
|
||||||
|
// Kai lebdi gor/dol
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.kaiImg,
|
||||||
|
y: H * 0.62 - 18,
|
||||||
|
duration: 2200,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subtilen glow pod Kai-em (shadow/aura)
|
||||||
|
this.kaiAura = this.add.ellipse(W * 0.78, H * 0.625, 180, 30, 0x44ffcc, 0.18)
|
||||||
|
.setDepth(9);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.kaiAura,
|
||||||
|
scaleX: { from: 0.85, to: 1.15 },
|
||||||
|
alpha: { from: 0.18, to: 0.08 },
|
||||||
|
duration: 2200,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// NASLOV IGRE
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// Subtitle zgoraj
|
||||||
|
this.add.text(W * 0.32, H * 0.12, 'KRVAVA ŽETEV', {
|
||||||
|
fontFamily: '"Georgia", serif',
|
||||||
|
fontSize: '28px',
|
||||||
|
color: '#cc6666',
|
||||||
|
letterSpacing: 12,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5).setDepth(10).setAlpha(0.85);
|
||||||
|
|
||||||
|
// Glavni naslov
|
||||||
|
const title = this.add.text(W * 0.32, H * 0.22, 'NOVA\nFARMA', {
|
||||||
|
fontFamily: '"Georgia", serif',
|
||||||
|
fontSize: '130px',
|
||||||
|
fontStyle: 'bold',
|
||||||
|
color: '#e8d5a3',
|
||||||
|
stroke: '#1a0a00',
|
||||||
|
strokeThickness: 14,
|
||||||
|
lineSpacing: -10,
|
||||||
|
align: 'center',
|
||||||
|
}).setOrigin(0.5).setDepth(10);
|
||||||
|
|
||||||
|
// Glow pulse na naslovu
|
||||||
|
this.tweens.add({
|
||||||
|
targets: title,
|
||||||
|
alpha: { from: 1.0, to: 0.85 },
|
||||||
|
duration: 2800,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tagline pod naslovom
|
||||||
|
this.add.text(W * 0.32, H * 0.41, '"Preživi. Najdi jo. Ne pozabi."', {
|
||||||
|
fontFamily: '"Georgia", serif',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
fontSize: '22px',
|
||||||
|
color: '#8899aa',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(0.5).setDepth(10);
|
||||||
|
|
||||||
|
// Horizontal divider
|
||||||
|
const div = this.add.graphics().setDepth(10);
|
||||||
|
div.lineStyle(1, 0x44ffcc, 0.35);
|
||||||
|
div.lineBetween(W * 0.12, H * 0.45, W * 0.52, H * 0.45);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// GUMBI
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const BTN_X = W * 0.32;
|
||||||
|
const BTN_Y_START = H * 0.54;
|
||||||
|
const BTN_GAP = 95;
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{ label: '▶ IGRAJ', id: 'play', color: '#44ffcc', hColor: '#ffffff' },
|
||||||
|
{ label: '⚙ NASTAVITVE', id: 'settings', color: '#aabbcc', hColor: '#ffffff' },
|
||||||
|
{ label: '✕ IZHOD', id: 'quit', color: '#cc6666', hColor: '#ff8888' },
|
||||||
|
];
|
||||||
|
|
||||||
|
this._btns = [];
|
||||||
|
|
||||||
|
buttons.forEach((b, i) => {
|
||||||
|
const y = BTN_Y_START + i * BTN_GAP;
|
||||||
|
|
||||||
|
// Gumb ozadje (Graphics)
|
||||||
|
const bg = this.add.graphics().setDepth(11);
|
||||||
|
const drawBg = (hover) => {
|
||||||
|
bg.clear();
|
||||||
|
bg.fillStyle(hover ? 0x1a2a20 : 0x0d1510, hover ? 0.95 : 0.80);
|
||||||
|
bg.fillRoundedRect(BTN_X - 190, y - 28, 380, 56, 12);
|
||||||
|
bg.lineStyle(hover ? 2 : 1, hover ? 0x44ffcc : 0x224433, hover ? 1.0 : 0.5);
|
||||||
|
bg.strokeRoundedRect(BTN_X - 190, y - 28, 380, 56, 12);
|
||||||
|
};
|
||||||
|
drawBg(false);
|
||||||
|
|
||||||
|
// Gumb tekst
|
||||||
|
const txt = this.add.text(BTN_X, y, b.label, {
|
||||||
|
fontFamily: '"Arial Black", Arial',
|
||||||
|
fontSize: '26px',
|
||||||
|
color: b.color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
letterSpacing: 3,
|
||||||
|
}).setOrigin(0.5).setDepth(12);
|
||||||
|
|
||||||
|
// Hit zone
|
||||||
|
const zone = this.add.zone(BTN_X, y, 380, 56)
|
||||||
|
.setDepth(13)
|
||||||
|
.setInteractive({ cursor: 'pointer' });
|
||||||
|
|
||||||
|
zone.on('pointerover', () => {
|
||||||
|
drawBg(true);
|
||||||
|
txt.setColor(b.hColor).setScale(1.05);
|
||||||
|
// Puščica
|
||||||
|
this.tweens.add({ targets: txt, x: BTN_X + 8, duration: 120, ease: 'Quad.out' });
|
||||||
|
});
|
||||||
|
zone.on('pointerout', () => {
|
||||||
|
drawBg(false);
|
||||||
|
txt.setColor(b.color).setScale(1.0);
|
||||||
|
this.tweens.add({ targets: txt, x: BTN_X, duration: 120, ease: 'Quad.out' });
|
||||||
|
});
|
||||||
|
zone.on('pointerdown', () => this._onButton(b.id));
|
||||||
|
|
||||||
|
this._btns.push({ bg, txt, zone });
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// TRIAL BADGE (levo zgoraj)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const trialBadge = this.add.image(30, 22, 'badge_trial')
|
||||||
|
.setOrigin(0, 0)
|
||||||
|
.setScale(0.32)
|
||||||
|
.setDepth(20);
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: trialBadge,
|
||||||
|
y: 28,
|
||||||
|
duration: 1600,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Purchase badge (desno spodaj, majhen)
|
||||||
|
this.add.image(W - 20, H - 20, 'badge_buy')
|
||||||
|
.setOrigin(1, 1)
|
||||||
|
.setScale(0.28)
|
||||||
|
.setDepth(20)
|
||||||
|
.setInteractive({ cursor: 'pointer' })
|
||||||
|
.on('pointerover', function () { this.setScale(0.31); })
|
||||||
|
.on('pointerout', function () { this.setScale(0.28); });
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// VERZIJA + AVTOR (spodaj levo)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.add.text(20, H - 20, 'v0.1.0-DEMO | © 2026 HipoDevil666', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#445544',
|
||||||
|
}).setOrigin(0, 1).setDepth(20);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// CONTROLS HINT (spodaj desno)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.add.text(W - 20, H - 20, 'WASD Gibanje | E Interakcija | SPACE Alfa Moč', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#334433',
|
||||||
|
}).setOrigin(1, 1).setDepth(20);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// FADE IN ob vstopu
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.cameras.main.fadeIn(800, 0, 0, 0);
|
||||||
|
|
||||||
|
// Akumuliraj čas za animacije
|
||||||
|
this._t = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// GUMB LOGIKA
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
_onButton(id) {
|
||||||
|
if (id === 'play') {
|
||||||
|
this.cameras.main.fadeOut(600, 0, 0, 0);
|
||||||
|
this.cameras.main.once('camerafadeoutcomplete', () => {
|
||||||
|
this.scene.start('GrassSceneClean');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (id === 'settings') {
|
||||||
|
// TODO: SettingsScene
|
||||||
|
this._showToast('Nastavitve kmalu! 🔧');
|
||||||
|
}
|
||||||
|
else if (id === 'quit') {
|
||||||
|
// Electron quit
|
||||||
|
if (typeof window !== 'undefined' && window.electronAPI) {
|
||||||
|
window.electronAPI.quit();
|
||||||
|
} else if (typeof process !== 'undefined') {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kratko sporočilo na zaslonu
|
||||||
|
_showToast(msg) {
|
||||||
|
const W = this.cameras.main.width;
|
||||||
|
const toast = this.add.text(W / 2, 80, msg, {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '22px',
|
||||||
|
color: '#44ffcc',
|
||||||
|
stroke: '#000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
backgroundColor: '#0a1a10cc',
|
||||||
|
padding: { x: 18, y: 8 },
|
||||||
|
}).setOrigin(0.5).setDepth(100).setAlpha(0);
|
||||||
|
|
||||||
|
this.tweens.add({
|
||||||
|
targets: toast,
|
||||||
|
alpha: { from: 0, to: 1 },
|
||||||
|
y: 70,
|
||||||
|
duration: 300,
|
||||||
|
ease: 'Quad.out',
|
||||||
|
onComplete: () => {
|
||||||
|
this.time.delayedCall(1400, () => {
|
||||||
|
this.tweens.add({
|
||||||
|
targets: toast,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 400,
|
||||||
|
onComplete: () => toast.destroy(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// UPDATE
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
update(time, delta) {
|
||||||
|
this._t += delta / 1000;
|
||||||
|
|
||||||
|
// Animiraj fireflye
|
||||||
|
this.fireflyGfx.clear();
|
||||||
|
for (const ff of this.fireflies) {
|
||||||
|
const pulse = 0.5 + 0.5 * Math.sin(this._t * ff.speed + ff.offset);
|
||||||
|
const a = 0.1 + pulse * 0.8;
|
||||||
|
const r = ff.r * (0.7 + 0.3 * pulse);
|
||||||
|
|
||||||
|
// Premik (leno lebdenje)
|
||||||
|
ff.x += Math.sin(this._t * 0.3 + ff.offset) * 0.4;
|
||||||
|
ff.y += Math.cos(this._t * 0.25 + ff.offset) * 0.3;
|
||||||
|
|
||||||
|
// Wrap
|
||||||
|
if (ff.x < 20) ff.x = this.cameras.main.width - 20;
|
||||||
|
if (ff.x > this.cameras.main.width - 20) ff.x = 20;
|
||||||
|
|
||||||
|
// Glow halo
|
||||||
|
this.fireflyGfx.fillStyle(ff.color, a * 0.15);
|
||||||
|
this.fireflyGfx.fillCircle(ff.x, ff.y, r * 3.5);
|
||||||
|
|
||||||
|
// Jedro
|
||||||
|
this.fireflyGfx.fillStyle(ff.color, a);
|
||||||
|
this.fireflyGfx.fillCircle(ff.x, ff.y, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,646 @@
|
|||||||
|
/**
|
||||||
|
* =============================================================
|
||||||
|
* UIScene.js — Nova Farma HUD Layer
|
||||||
|
* =============================================================
|
||||||
|
* Overlay scene (runs on top of GrassScene_Clean)
|
||||||
|
* Contains ALL UI elements:
|
||||||
|
*
|
||||||
|
* TOP LEFT: Trial Version badge + HP bar + energy bar
|
||||||
|
* TOP RIGHT: Weather widget (ura + vreme)
|
||||||
|
* BOT LEFT: Dan/Ura text
|
||||||
|
* BOT CENTER: Hotbar (7 slotov) + action button
|
||||||
|
* BOT RIGHT: Minimap compass ring
|
||||||
|
* POPUP: Inventory panel [I] toggle
|
||||||
|
* OVERLAY: Health Critical pulse (pri < 25% HP)
|
||||||
|
* =============================================================
|
||||||
|
*/
|
||||||
export default class UIScene extends Phaser.Scene {
|
export default class UIScene extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ key: 'UIScene', active: false });
|
super({ key: 'UIScene', active: false }); // Zaženemo ročno iz GrassScene
|
||||||
|
|
||||||
|
// Stanje igre (posodablja se iz GrassScene prek eventos)
|
||||||
|
this.gameState = {
|
||||||
|
hp: 100,
|
||||||
|
maxHp: 100,
|
||||||
|
energy: 100,
|
||||||
|
maxEnergy: 100,
|
||||||
|
day: 1,
|
||||||
|
hour: 6,
|
||||||
|
minute: 0,
|
||||||
|
weather: 'rain', // 'sun', 'rain', 'storm', 'cloud'
|
||||||
|
inventoryOpen: false,
|
||||||
|
hotbarItems: [null, null, null, null, null, null, null], // 7 slotov
|
||||||
|
activeSlot: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
preload() {
|
preload() {
|
||||||
this.load.path = 'assets/';
|
this.load.path = 'assets/';
|
||||||
// Load UI Assets here (moved from GrassScene)
|
|
||||||
|
// ── Vse UI assete naložimo tukaj ──
|
||||||
this.load.image('ui_health_bar', 'DEMO_FAZA1/UI/health_bar.png');
|
this.load.image('ui_health_bar', 'DEMO_FAZA1/UI/health_bar.png');
|
||||||
this.load.image('ui_weather_widget', 'DEMO_FAZA1/UI/weather_widget.png');
|
this.load.image('ui_health_critical', 'DEMO_FAZA1/UI/health_critical.png');
|
||||||
|
this.load.image('ui_weather', 'DEMO_FAZA1/UI/weather_widget.png');
|
||||||
this.load.image('ui_minimap', 'DEMO_FAZA1/UI/minimap_frame.png');
|
this.load.image('ui_minimap', 'DEMO_FAZA1/UI/minimap_frame.png');
|
||||||
this.load.image('ui_hotbar', 'DEMO_FAZA1/UI/hotbar_background.png');
|
this.load.image('ui_hotbar', 'DEMO_FAZA1/UI/hotbar_background.png');
|
||||||
|
this.load.image('ui_slot', 'DEMO_FAZA1/UI/inventory_slot.png');
|
||||||
this.load.image('ui_action_btn', 'DEMO_FAZA1/UI/action_btn.png');
|
this.load.image('ui_action_btn', 'DEMO_FAZA1/UI/action_btn.png');
|
||||||
this.load.image('ui_badge_trial', 'DEMO_FAZA1/UI/badge_trial.png');
|
this.load.image('ui_badge_trial', 'DEMO_FAZA1/UI/badge_trial.png');
|
||||||
|
this.load.image('ui_badge_buy', 'DEMO_FAZA1/UI/badge_purchase.png');
|
||||||
|
this.load.image('ui_inventory', 'DEMO_FAZA1/UI/inventory_panel.png');
|
||||||
|
this.load.image('ui_dialog', 'DEMO_FAZA1/UI/dialog_panel.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
// Simple UI Layout
|
const W = this.cameras.main.width; // 1920
|
||||||
const width = this.cameras.main.width;
|
const H = this.cameras.main.height; // 1080
|
||||||
const height = this.cameras.main.height;
|
const PAD = 24;
|
||||||
const PAD = 60; // Increased padding for better visibility
|
|
||||||
|
|
||||||
// 0. TRIAL BADGE (Top Center - The "Hook")
|
// ─────────────────────────────────────────────────────────────────
|
||||||
this.add.image(width / 2, PAD, 'ui_badge_trial')
|
// TOP LEFT — Trial badge + HP + Energy
|
||||||
.setOrigin(0.5, 0)
|
// ─────────────────────────────────────────────────────────────────
|
||||||
.setScale(1.0) // Increased scale
|
|
||||||
.setDepth(100); // Ensure it's on top
|
|
||||||
|
|
||||||
// 1. Health (Top Left)
|
// Trial Version Badge
|
||||||
let health = this.add.image(PAD, PAD, 'ui_health_bar').setOrigin(0, 0).setScale(1.0);
|
this.trialBadge = this.add.image(PAD, PAD, 'ui_badge_trial')
|
||||||
|
.setOrigin(0, 0)
|
||||||
|
.setScale(0.38)
|
||||||
|
.setDepth(1000);
|
||||||
|
|
||||||
// 2. Weather (Top Right)
|
// Badge lebdi animacija
|
||||||
let weather = this.add.image(width - PAD, PAD, 'ui_weather_widget').setOrigin(1, 0).setScale(1.0);
|
this.tweens.add({
|
||||||
|
targets: this.trialBadge,
|
||||||
|
y: PAD + 6,
|
||||||
|
duration: 1800,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
|
||||||
// 3. Minimap (Bottom Right)
|
// ── HP Bar (health_bar.png je vertikalni triptih: full/empty/empty) ─
|
||||||
let minimap = this.add.image(width - PAD, height - PAD, 'ui_minimap').setOrigin(1, 1).setScale(0.9);
|
// Spritesheet je 3 vrstice: top=HP full, mid=HP empty, bot=Energy
|
||||||
|
// Uporabljamo health_bar.png kot ozadje in rišemo fill čez njega
|
||||||
|
const HP_X = PAD + 10;
|
||||||
|
const HP_Y = PAD + 130;
|
||||||
|
const HP_W = 420;
|
||||||
|
const HP_H = 36;
|
||||||
|
|
||||||
// 4. Hotbar (Bottom Center)
|
// Ozadje HP (temno rdeče)
|
||||||
let hotbar = this.add.image(width / 2, height - PAD, 'ui_hotbar').setOrigin(0.5, 1).setScale(1.0);
|
this.hpBg = this.add.graphics().setDepth(1001);
|
||||||
|
this.hpBg.fillStyle(0x4a0000, 1.0);
|
||||||
|
this.hpBg.fillRoundedRect(HP_X, HP_Y, HP_W, HP_H, 8);
|
||||||
|
|
||||||
// 5. Action Button (Right of Hotbar)
|
// HP Fill (svetlo rdeča)
|
||||||
let action = this.add.image(width / 2 + 400, height - PAD, 'ui_action_btn').setOrigin(0.5, 1).setScale(1.0);
|
this.hpFill = this.add.graphics().setDepth(1002);
|
||||||
|
|
||||||
// Debug Text
|
// HP ikona (skull) – kratko besedilo label
|
||||||
this.add.text(width / 2, 50, 'HUD LAYER ACTIVE', {
|
this.add.text(HP_X - 5, HP_Y + HP_H / 2, '❤️', { fontSize: '22px' })
|
||||||
fontSize: '24px', fill: '#00ff00', stroke: '#000', strokeThickness: 4
|
.setOrigin(1, 0.5).setDepth(1003);
|
||||||
}).setOrigin(0.5);
|
|
||||||
|
// HP vrednost text
|
||||||
|
this.hpText = this.add.text(HP_X + HP_W / 2, HP_Y + HP_H / 2, '100 / 100', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#ffffff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(1004);
|
||||||
|
|
||||||
|
// ── Energy Bar ─────────────────────────────────────────────────────
|
||||||
|
const EN_X = HP_X;
|
||||||
|
const EN_Y = HP_Y + HP_H + 8;
|
||||||
|
const EN_W = HP_W;
|
||||||
|
const EN_H = 24;
|
||||||
|
|
||||||
|
this.energyBg = this.add.graphics().setDepth(1001);
|
||||||
|
this.energyBg.fillStyle(0x1a1a00, 1.0);
|
||||||
|
this.energyBg.fillRoundedRect(EN_X, EN_Y, EN_W, EN_H, 6);
|
||||||
|
|
||||||
|
this.energyFill = this.add.graphics().setDepth(1002);
|
||||||
|
|
||||||
|
this.add.text(EN_X - 5, EN_Y + EN_H / 2, '⚡', { fontSize: '16px' })
|
||||||
|
.setOrigin(1, 0.5).setDepth(1003);
|
||||||
|
|
||||||
|
this.energyText = this.add.text(EN_X + EN_W / 2, EN_Y + EN_H / 2, '100%', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#ffee44',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(1004);
|
||||||
|
|
||||||
|
// Shrani pozicije za redraw
|
||||||
|
this._hpBar = { x: HP_X, y: HP_Y, w: HP_W, h: HP_H };
|
||||||
|
this._enBar = { x: EN_X, y: EN_Y, w: EN_W, h: EN_H };
|
||||||
|
|
||||||
|
// Prvič nariši
|
||||||
|
this._redrawBars();
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// TOP RIGHT — Weather Widget (ura + vreme)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.weatherWidget = this.add.image(W - PAD, PAD, 'ui_weather')
|
||||||
|
.setOrigin(1, 0)
|
||||||
|
.setScale(0.55)
|
||||||
|
.setDepth(1000);
|
||||||
|
|
||||||
|
// Ura text (čez weather widget)
|
||||||
|
this.timeText = this.add.text(W - PAD - 62, PAD + 68, '06:00', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '28px',
|
||||||
|
color: '#ff3333',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(1005);
|
||||||
|
|
||||||
|
// Dan label (pod uro)
|
||||||
|
this.dayText = this.add.text(W - PAD - 135, PAD + 10, 'DAN 1', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#ffdd44',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5, 0).setDepth(1005);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// BOTTOM CENTER — Hotbar (7 slotov)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const HOTBAR_SCALE = 0.72;
|
||||||
|
this.hotbarBg = this.add.image(W / 2, H - PAD, 'ui_hotbar')
|
||||||
|
.setOrigin(0.5, 1)
|
||||||
|
.setScale(HOTBAR_SCALE)
|
||||||
|
.setDepth(1000);
|
||||||
|
|
||||||
|
// Hotbar dimenzije (hotbar_background.png = ~1120×160px)
|
||||||
|
const HOTBAR_W = 1120 * HOTBAR_SCALE;
|
||||||
|
const HOTBAR_H = 160 * HOTBAR_SCALE;
|
||||||
|
const HOT_START_X = W / 2 - HOTBAR_W / 2 + 56;
|
||||||
|
const HOT_Y = H - PAD - HOTBAR_H / 2;
|
||||||
|
const SLOT_W = (HOTBAR_W - 112) / 7;
|
||||||
|
|
||||||
|
// Shrani za event listener
|
||||||
|
this._hotbarW = HOTBAR_W;
|
||||||
|
this._hotbarH = HOTBAR_H;
|
||||||
|
this._slotW = SLOT_W;
|
||||||
|
this._hotY = HOT_Y;
|
||||||
|
|
||||||
|
this.hotbarSlots = [];
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const sx = HOT_START_X + i * SLOT_W + SLOT_W / 2;
|
||||||
|
|
||||||
|
// Slot number label
|
||||||
|
const numLabel = this.add.text(sx - SLOT_W * 0.35, HOT_Y - HOTBAR_H * 0.32, `${i + 1}`, {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#888888',
|
||||||
|
}).setOrigin(0, 0).setDepth(1002);
|
||||||
|
|
||||||
|
// Active highlight
|
||||||
|
const highlight = this.add.graphics().setDepth(1001);
|
||||||
|
if (i === this.gameState.activeSlot) {
|
||||||
|
highlight.lineStyle(3, 0xffdd00, 1.0);
|
||||||
|
highlight.strokeRoundedRect(
|
||||||
|
sx - SLOT_W / 2 + 4, HOT_Y - HOTBAR_H / 2 + 4,
|
||||||
|
SLOT_W - 8, HOTBAR_H - 8, 6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.hotbarSlots.push({ x: sx, y: HOT_Y, highlight, numLabel });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Action Button (desno od hotbara) ────────────────────────────
|
||||||
|
this.actionBtn = this.add.image(W / 2 + HOTBAR_W / 2 + 50, H - PAD - 40, 'ui_action_btn')
|
||||||
|
.setOrigin(0.5, 0.5)
|
||||||
|
.setScale(0.9)
|
||||||
|
.setDepth(1000)
|
||||||
|
.setInteractive({ cursor: 'pointer' });
|
||||||
|
|
||||||
|
this.actionBtn.on('pointerover', () => this.actionBtn.setTint(0xffdd88));
|
||||||
|
this.actionBtn.on('pointerout', () => this.actionBtn.clearTint());
|
||||||
|
|
||||||
|
// Crafting label
|
||||||
|
this.add.text(this.actionBtn.x, H - PAD + 2, 'CRAFTING', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#aaaaaa',
|
||||||
|
stroke: '#000',
|
||||||
|
strokeThickness: 2,
|
||||||
|
}).setOrigin(0.5, 0).setDepth(1002);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// BOTTOM RIGHT — Minimap Compass
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const MM_SCALE = 0.55;
|
||||||
|
const MM_SIZE = 256 * MM_SCALE; // compass je 256×256
|
||||||
|
|
||||||
|
this.minimapFrame = this.add.image(W - PAD, H - PAD, 'ui_minimap')
|
||||||
|
.setOrigin(1, 1)
|
||||||
|
.setScale(MM_SCALE)
|
||||||
|
.setDepth(1000);
|
||||||
|
|
||||||
|
// Minimap inner black fill (bg)
|
||||||
|
this.minimapBg = this.add.graphics().setDepth(999);
|
||||||
|
const mmCx = W - PAD - MM_SIZE / 2;
|
||||||
|
const mmCy = H - PAD - MM_SIZE / 2;
|
||||||
|
this.minimapBg.fillStyle(0x001a33, 0.85);
|
||||||
|
this.minimapBg.fillCircle(mmCx, mmCy, MM_SIZE / 2 - 12);
|
||||||
|
|
||||||
|
// Kai dot on minimap
|
||||||
|
this.minimapKaiDot = this.add.graphics().setDepth(1001);
|
||||||
|
this.minimapKaiDot.fillStyle(0x44ffaa, 1.0);
|
||||||
|
this.minimapKaiDot.fillCircle(mmCx, mmCy, 5);
|
||||||
|
|
||||||
|
// Store for update
|
||||||
|
this._mmCx = mmCx;
|
||||||
|
this._mmCy = mmCy;
|
||||||
|
this._mmR = MM_SIZE / 2 - 16;
|
||||||
|
|
||||||
|
// "MAP" label
|
||||||
|
this.add.text(mmCx, mmCy + MM_SIZE / 2 - 8, 'MAPA', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#556655',
|
||||||
|
}).setOrigin(0.5, 1).setDepth(1002);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// INVENTORY POPUP (I tipka toggle)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const INV_SCALE = 0.75;
|
||||||
|
const INV_W = 600 * INV_SCALE;
|
||||||
|
const INV_H = 600 * INV_SCALE;
|
||||||
|
|
||||||
|
this.inventoryPanel = this.add.image(W / 2, H / 2, 'ui_inventory')
|
||||||
|
.setOrigin(0.5, 0.5)
|
||||||
|
.setScale(INV_SCALE)
|
||||||
|
.setDepth(5000)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
|
// Inventory title
|
||||||
|
this.inventoryTitle = this.add.text(W / 2, H / 2 - INV_H / 2 + 28, 'INVENTAR', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '22px',
|
||||||
|
color: '#e8d5a3',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(5001).setVisible(false);
|
||||||
|
|
||||||
|
// Close hint
|
||||||
|
this.inventoryClose = this.add.text(W / 2, H / 2 + INV_H / 2 - 20, '[I] Zapri', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#888888',
|
||||||
|
}).setOrigin(0.5, 1).setDepth(5001).setVisible(false);
|
||||||
|
|
||||||
|
// Dim overlay when inventory open
|
||||||
|
this.invDim = this.add.rectangle(W / 2, H / 2, W, H, 0x000000, 0.4)
|
||||||
|
.setDepth(4999)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
|
// [I] tipka toggle
|
||||||
|
this.input.keyboard.on('keydown-I', () => this.toggleInventory());
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// HEALTH CRITICAL OVERLAY (pulse ko HP < 25%)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.criticalOverlay = this.add.image(W / 2, H / 2, 'ui_health_critical')
|
||||||
|
.setOrigin(0.5, 0.5)
|
||||||
|
.setScale(Math.max(W / 512, H / 512)) // Pokrije ekran
|
||||||
|
.setAlpha(0)
|
||||||
|
.setDepth(4000);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// DIALOG BOX (spodaj, za govor NPCjev)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const DLG_W = 900;
|
||||||
|
const DLG_H = 160;
|
||||||
|
|
||||||
|
this.dialogPanel = this.add.image(W / 2, H - 20, 'ui_dialog')
|
||||||
|
.setOrigin(0.5, 1)
|
||||||
|
.setDisplaySize(DLG_W, DLG_H)
|
||||||
|
.setDepth(6000)
|
||||||
|
.setVisible(false);
|
||||||
|
|
||||||
|
this.dialogText = this.add.text(W / 2, H - 30, '', {
|
||||||
|
fontFamily: '"Courier New", monospace',
|
||||||
|
fontSize: '20px',
|
||||||
|
color: '#2a1a00',
|
||||||
|
wordWrap: { width: DLG_W - 80 },
|
||||||
|
align: 'left',
|
||||||
|
}).setOrigin(0.5, 1).setDepth(6001).setVisible(false);
|
||||||
|
|
||||||
|
this.dialogName = this.add.text(W / 2 - DLG_W / 2 + 40, H - DLG_H - 10, '', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#ffdd88',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(0, 1).setDepth(6001).setVisible(false);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// KEYBOARD SHORTCUTS HINT (spodaj levo)
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.add.text(PAD, H - PAD, '[I] Inventar [B] Gradnja [ESC] Pavza', {
|
||||||
|
fontFamily: 'Arial',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#444444',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 2,
|
||||||
|
}).setOrigin(0, 1).setDepth(1000);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// VZPOSTAVI KOMUNIKACIJO Z GRASSSCENE
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
this.game.events.on('ui-update-hp', (hp) => {
|
||||||
|
this.gameState.hp = Phaser.Math.Clamp(hp, 0, this.gameState.maxHp);
|
||||||
|
this._redrawBars();
|
||||||
|
this._checkCritical();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.events.on('ui-update-energy', (energy) => {
|
||||||
|
this.gameState.energy = Phaser.Math.Clamp(energy, 0, this.gameState.maxEnergy);
|
||||||
|
this._redrawBars();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.events.on('ui-update-time', (day, hour, minute) => {
|
||||||
|
this.gameState.day = day;
|
||||||
|
this.gameState.hour = hour;
|
||||||
|
this.gameState.minute = minute;
|
||||||
|
this._updateTimeDisplay();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.events.on('ui-show-dialog', (name, text) => {
|
||||||
|
this.showDialog(name, text);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.game.events.on('ui-hide-dialog', () => {
|
||||||
|
this.hideDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Hotbar update iz InventorySystem ──────────────────────────────
|
||||||
|
// Hotbar item text labels (nad sloti)
|
||||||
|
this._hotbarItemTexts = [];
|
||||||
|
this._hotbarCountTexts = [];
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const { x, y } = this.hotbarSlots[i];
|
||||||
|
const icon = this.add.text(x, y - 4, '', {
|
||||||
|
fontSize: '26px',
|
||||||
|
align: 'center',
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(1005);
|
||||||
|
|
||||||
|
const cnt = this.add.text(x + 16, y + 14, '', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '11px',
|
||||||
|
color: '#ffdd44',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(0.5, 0.5).setDepth(1006);
|
||||||
|
|
||||||
|
this._hotbarItemTexts.push(icon);
|
||||||
|
this._hotbarCountTexts.push(cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gold counter (spodaj levo od hotbara)
|
||||||
|
this.goldText = this.add.text(W / 2 - HOTBAR_W / 2, H - PAD - 85, '💰 0g', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '16px',
|
||||||
|
color: '#ffd700',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0, 0.5).setDepth(1005);
|
||||||
|
|
||||||
|
// Poslušaj hotbar updete iz InventorySystem
|
||||||
|
this.events.on('hotbarUpdate', (data) => {
|
||||||
|
const { slots, activeSlot, gold } = data;
|
||||||
|
|
||||||
|
// Posodobi slote
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const slot = slots[i];
|
||||||
|
const hl = this.hotbarSlots[i].highlight;
|
||||||
|
hl.clear();
|
||||||
|
|
||||||
|
// Active slot highlight
|
||||||
|
const { x, y } = this.hotbarSlots[i];
|
||||||
|
const SW = this._slotW;
|
||||||
|
const SH = this._hotbarH;
|
||||||
|
if (i === activeSlot) {
|
||||||
|
hl.lineStyle(3, 0xffdd00, 1.0);
|
||||||
|
hl.strokeRoundedRect(
|
||||||
|
x - SW / 2 + 4, y - SH / 2 + 4,
|
||||||
|
SW - 8, SH - 8, 6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon in count
|
||||||
|
if (slot) {
|
||||||
|
this._hotbarItemTexts[i].setText(slot.icon || '');
|
||||||
|
this._hotbarCountTexts[i].setText(
|
||||||
|
slot.stackable && slot.count > 0 ? `×${slot.count}` : ''
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._hotbarItemTexts[i].setText('');
|
||||||
|
this._hotbarCountTexts[i].setText('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gold
|
||||||
|
if (this.goldText) this.goldText.setText(`💰 ${gold}g`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Time update iz DayNightSystem ─────────────────────────────────
|
||||||
|
this.events.on('timeChanged', (data) => {
|
||||||
|
if (this.timeText) {
|
||||||
|
this.timeText.setText(data.timeStr || '06:00');
|
||||||
|
}
|
||||||
|
if (this.dayText) {
|
||||||
|
this.dayText.setText(`DAN ${data.day}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Water update iz WaterSystem ───────────────────────────────────
|
||||||
|
// Vedro indikator (blizu hotbara)
|
||||||
|
this.waterHUD = this.add.text(W / 2 + HOTBAR_W / 2 - 60, H - PAD - 85, '💧 3/5L', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#44aaff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(1, 0.5).setDepth(1005);
|
||||||
|
|
||||||
|
this.events.on('waterChanged', (data) => {
|
||||||
|
if (this.waterHUD) {
|
||||||
|
const pct = data.can / data.canMax;
|
||||||
|
const col = pct > 0.5 ? '#44aaff' : pct > 0.2 ? '#ffaa00' : '#ff4444';
|
||||||
|
this.waterHUD
|
||||||
|
.setText(`💧 ${data.can}/${data.canMax}L`)
|
||||||
|
.setColor(col);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// UIScene teče vzporedno z GrassScene, prinese se na vrh
|
||||||
|
this.scene.bringToTop();
|
||||||
|
|
||||||
|
console.log('✅ UIScene initialized with all elements!');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// HELPERS
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
/** Nariši HP in Energy bar fills */
|
||||||
|
_redrawBars() {
|
||||||
|
const { hp, maxHp, energy, maxEnergy } = this.gameState;
|
||||||
|
|
||||||
|
// HP bar
|
||||||
|
const hpPct = hp / maxHp;
|
||||||
|
const { x, y, w, h } = this._hpBar;
|
||||||
|
this.hpFill.clear();
|
||||||
|
|
||||||
|
// Barva glede na HP %
|
||||||
|
const hpColor = hpPct > 0.6 ? 0xcc2222 :
|
||||||
|
hpPct > 0.3 ? 0xdd7700 : 0xff0000;
|
||||||
|
|
||||||
|
this.hpFill.fillStyle(hpColor, 1.0);
|
||||||
|
this.hpFill.fillRoundedRect(x, y, Math.max(4, w * hpPct), h, 8);
|
||||||
|
|
||||||
|
// HP outer border
|
||||||
|
this.hpFill.lineStyle(2, 0x660000, 1.0);
|
||||||
|
this.hpFill.strokeRoundedRect(x, y, w, h, 8);
|
||||||
|
|
||||||
|
this.hpText.setText(`${Math.round(hp)} / ${maxHp}`);
|
||||||
|
|
||||||
|
// Energy bar
|
||||||
|
const enPct = energy / maxEnergy;
|
||||||
|
const e = this._enBar;
|
||||||
|
this.energyFill.clear();
|
||||||
|
|
||||||
|
const enColor = enPct > 0.5 ? 0xddcc00 :
|
||||||
|
enPct > 0.2 ? 0xdd8800 : 0xff4400;
|
||||||
|
|
||||||
|
this.energyFill.fillStyle(enColor, 1.0);
|
||||||
|
this.energyFill.fillRoundedRect(e.x, e.y, Math.max(4, e.w * enPct), e.h, 6);
|
||||||
|
this.energyFill.lineStyle(2, 0x444400, 1.0);
|
||||||
|
this.energyFill.strokeRoundedRect(e.x, e.y, e.w, e.h, 6);
|
||||||
|
|
||||||
|
this.energyText.setText(`${Math.round(energy)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Preveri kritičen HP in prikaži overlay */
|
||||||
|
_checkCritical() {
|
||||||
|
const pct = this.gameState.hp / this.gameState.maxHp;
|
||||||
|
if (pct < 0.25) {
|
||||||
|
// Pulse animacija
|
||||||
|
if (!this._criticalTween || !this._criticalTween.isPlaying()) {
|
||||||
|
this._criticalTween = this.tweens.add({
|
||||||
|
targets: this.criticalOverlay,
|
||||||
|
alpha: { from: 0, to: 0.55 },
|
||||||
|
duration: 600,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: -1,
|
||||||
|
ease: 'Sine.easeInOut',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ugasni
|
||||||
|
if (this._criticalTween) {
|
||||||
|
this._criticalTween.stop();
|
||||||
|
this._criticalTween = null;
|
||||||
|
}
|
||||||
|
this.criticalOverlay.setAlpha(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Posodobi uro + dan display */
|
||||||
|
_updateTimeDisplay() {
|
||||||
|
const { day, hour, minute } = this.gameState;
|
||||||
|
const hStr = String(hour).padStart(2, '0');
|
||||||
|
const mStr = String(minute).padStart(2, '0');
|
||||||
|
this.timeText.setText(`${hStr}:${mStr}`);
|
||||||
|
this.dayText.setText(`DAN ${day}`);
|
||||||
|
|
||||||
|
// Ura barva: rdeča ponoči, rumena podnevi
|
||||||
|
const isNight = hour >= 21 || hour < 5;
|
||||||
|
this.timeText.setColor(isNight ? '#8899ff' : '#ff3333');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggle Inventory popup */
|
||||||
|
toggleInventory() {
|
||||||
|
this.gameState.inventoryOpen = !this.gameState.inventoryOpen;
|
||||||
|
const open = this.gameState.inventoryOpen;
|
||||||
|
|
||||||
|
this.inventoryPanel.setVisible(open);
|
||||||
|
this.inventoryTitle.setVisible(open);
|
||||||
|
this.inventoryClose.setVisible(open);
|
||||||
|
this.invDim.setVisible(open);
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
// Slide in animacija
|
||||||
|
this.inventoryPanel.setAlpha(0).setScale(0.65);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: this.inventoryPanel,
|
||||||
|
alpha: 1,
|
||||||
|
scale: 0.75,
|
||||||
|
duration: 200,
|
||||||
|
ease: 'Back.out',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prikaži dialog box (typewriter) */
|
||||||
|
showDialog(name, text) {
|
||||||
|
this.dialogPanel.setVisible(true);
|
||||||
|
this.dialogName.setVisible(true).setText(name || '');
|
||||||
|
this.dialogText.setVisible(true).setText('');
|
||||||
|
|
||||||
|
// Typewriter efekt
|
||||||
|
let idx = 0;
|
||||||
|
if (this._typewriterTimer) this._typewriterTimer.remove();
|
||||||
|
this._typewriterTimer = this.time.addEvent({
|
||||||
|
delay: 35,
|
||||||
|
repeat: text.length - 1,
|
||||||
|
callback: () => {
|
||||||
|
this.dialogText.setText(text.substring(0, ++idx));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Skrij dialog box */
|
||||||
|
hideDialog() {
|
||||||
|
if (this._typewriterTimer) this._typewriterTimer.remove();
|
||||||
|
this.dialogPanel.setVisible(false);
|
||||||
|
this.dialogText.setVisible(false);
|
||||||
|
this.dialogName.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// UPDATE — Minimap Kai dot
|
||||||
|
// =========================================================
|
||||||
|
update() {
|
||||||
|
// Posodaibaj Kai dot na minimapu
|
||||||
|
const grassScene = this.scene.get('GrassSceneClean');
|
||||||
|
if (!grassScene || !grassScene.kai) return;
|
||||||
|
|
||||||
|
const kai = grassScene.kai;
|
||||||
|
|
||||||
|
// Svet bounds (iz GrassScene)
|
||||||
|
const worldW = grassScene.worldWidth || 10400;
|
||||||
|
const worldH = grassScene.worldHeight || 10400;
|
||||||
|
|
||||||
|
// Normaliziraj Kai pozicijo [0..1]
|
||||||
|
const nx = kai.x / worldW;
|
||||||
|
const ny = kai.y / worldH;
|
||||||
|
|
||||||
|
// Mapiraj na minimap krog
|
||||||
|
const angle = Math.atan2(ny - 0.5, nx - 0.5);
|
||||||
|
const dist = Math.min(Math.sqrt((nx - 0.5) ** 2 + (ny - 0.5) ** 2) * 2, 1);
|
||||||
|
|
||||||
|
const dotX = this._mmCx + Math.cos(angle) * dist * this._mmR;
|
||||||
|
const dotY = this._mmCy + Math.sin(angle) * dist * this._mmR;
|
||||||
|
|
||||||
|
this.minimapKaiDot.clear();
|
||||||
|
this.minimapKaiDot.fillStyle(0x44ffaa, 1.0);
|
||||||
|
this.minimapKaiDot.fillCircle(dotX, dotY, 5);
|
||||||
|
|
||||||
|
// Pulsirajoč rob na dotiku
|
||||||
|
this.minimapKaiDot.lineStyle(2, 0xffffff, 0.6);
|
||||||
|
this.minimapKaiDot.strokeCircle(dotX, dotY, 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,43 +2,38 @@
|
|||||||
* ============================================================
|
* ============================================================
|
||||||
* BUILDING SYSTEM — Nova Farma
|
* BUILDING SYSTEM — Nova Farma
|
||||||
* ============================================================
|
* ============================================================
|
||||||
* Handles:
|
* [B] = vklopi / izklopi build mode
|
||||||
* - Build Mode (toggle with [B] key or UI button)
|
* [Esc] = izhod
|
||||||
* - Ghost preview image that follows the cursor
|
* Klik = postavi stavbo
|
||||||
* - Click to place building at cursor position
|
*
|
||||||
* - Y-sort depth so Kai walks behind / in front of buildings
|
* UI meni je ODSTRANJEN — stavbe se dodajajo programsko
|
||||||
* - Physics static body so Kai cannot walk through walls
|
* prek: buildingSystem.selectBuilding('sotor')
|
||||||
* - Side-panel Build UI with selectable building icons
|
|
||||||
* ============================================================
|
* ============================================================
|
||||||
*/
|
*/
|
||||||
export default class BuildingSystem {
|
export default class BuildingSystem {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Phaser.Scene} scene - The scene this system runs in
|
* @param {Phaser.Scene} scene
|
||||||
* @param {Phaser.Physics.Arcade.Sprite} kai - Player sprite reference
|
* @param {Phaser.Physics.Arcade.Sprite} kai
|
||||||
*/
|
*/
|
||||||
constructor(scene, kai) {
|
constructor(scene, kai) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.kai = kai;
|
this.kai = kai;
|
||||||
|
|
||||||
// Build mode state
|
this.active = false;
|
||||||
this.active = false; // Is build mode ON?
|
this.selectedKey = null;
|
||||||
this.selectedKey = null; // Currently selected building texture key
|
|
||||||
|
|
||||||
// Ghost preview sprite
|
|
||||||
this.ghost = null;
|
this.ghost = null;
|
||||||
|
this.placed = [];
|
||||||
|
|
||||||
// Group of all placed buildings (static physics bodies)
|
// Static physics group za vse postavljene stavbe
|
||||||
this.buildingsGroup = scene.physics.add.staticGroup();
|
this.buildingsGroup = scene.physics.add.staticGroup();
|
||||||
|
|
||||||
// Catalogue of buildable items
|
// Katalog stavb — tukaj dodajamo nove
|
||||||
// { key, label, colliderW, colliderH, colliderOffsetX, colliderOffsetY, scale }
|
|
||||||
// Scale is % of Kai height (64px).
|
|
||||||
this.catalogue = [
|
this.catalogue = [
|
||||||
{
|
{
|
||||||
key: 'sotor',
|
key: 'sotor',
|
||||||
label: 'Šotor',
|
label: 'Šotor',
|
||||||
scale: 2.5, // 2.5× Kai → 160px tall
|
scale: 2.5, // 2.5× Kai (64px) = 160px
|
||||||
colliderW: 160,
|
colliderW: 160,
|
||||||
colliderH: 40,
|
colliderH: 40,
|
||||||
colliderOffsetX: -80,
|
colliderOffsetX: -80,
|
||||||
@@ -46,8 +41,8 @@ export default class BuildingSystem {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'campfire',
|
key: 'campfire',
|
||||||
label: 'Ogenj',
|
label: 'Taborni ogenj',
|
||||||
scale: 1.0, // Same height as Kai
|
scale: 1.0,
|
||||||
colliderW: 50,
|
colliderW: 50,
|
||||||
colliderH: 20,
|
colliderH: 20,
|
||||||
colliderOffsetX: -25,
|
colliderOffsetX: -25,
|
||||||
@@ -64,7 +59,7 @@ export default class BuildingSystem {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'foundation_concrete',
|
key: 'foundation_concrete',
|
||||||
label: 'Temelj',
|
label: 'Betonski temelj',
|
||||||
scale: 2.0,
|
scale: 2.0,
|
||||||
colliderW: 120,
|
colliderW: 120,
|
||||||
colliderH: 20,
|
colliderH: 20,
|
||||||
@@ -73,160 +68,37 @@ export default class BuildingSystem {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Build UI panel (DOM container)
|
|
||||||
this.panel = null;
|
|
||||||
|
|
||||||
// Track placed buildings for saving/loading later
|
|
||||||
this.placed = [];
|
|
||||||
|
|
||||||
this._setupUI();
|
|
||||||
this._setupInput();
|
this._setupInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// UI PANEL (Fixed to screen, top-right)
|
// KEYBOARD: [B] toggle, [Esc] izhod
|
||||||
// ==========================================
|
|
||||||
_setupUI() {
|
|
||||||
const scene = this.scene;
|
|
||||||
const CAM_W = scene.cameras.main.width;
|
|
||||||
const PANEL_W = 80;
|
|
||||||
const PANEL_PADDING = 8;
|
|
||||||
const BTN_SIZE = 60;
|
|
||||||
const BTN_GAP = 12;
|
|
||||||
|
|
||||||
// ── Background Panel ─────────────────────────────────────────────
|
|
||||||
this.panel = scene.add.graphics()
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(20000);
|
|
||||||
|
|
||||||
const panelH = this.catalogue.length * (BTN_SIZE + BTN_GAP) + PANEL_PADDING * 2 + 40;
|
|
||||||
const panelX = CAM_W - PANEL_W - 16;
|
|
||||||
const panelY = 120;
|
|
||||||
|
|
||||||
// Dark translucent panel
|
|
||||||
this.panel.fillStyle(0x0a0a0f, 0.85);
|
|
||||||
this.panel.fillRoundedRect(panelX, panelY, PANEL_W, panelH, 10);
|
|
||||||
this.panel.lineStyle(2, 0x44ffaa, 0.6);
|
|
||||||
this.panel.strokeRoundedRect(panelX, panelY, PANEL_W, panelH, 10);
|
|
||||||
|
|
||||||
// "Build" label
|
|
||||||
scene.add.text(
|
|
||||||
panelX + PANEL_W / 2,
|
|
||||||
panelY + 10,
|
|
||||||
'[B]',
|
|
||||||
{ fontSize: '13px', color: '#44ffaa', fontFamily: 'Arial', align: 'center' }
|
|
||||||
).setOrigin(0.5, 0).setScrollFactor(0).setDepth(20001);
|
|
||||||
|
|
||||||
// ── Building Buttons ──────────────────────────────────────────────
|
|
||||||
this.catalogue.forEach((item, i) => {
|
|
||||||
const btnX = panelX + PANEL_PADDING;
|
|
||||||
const btnY = panelY + 40 + i * (BTN_SIZE + BTN_GAP);
|
|
||||||
const btnW = PANEL_W - PANEL_PADDING * 2;
|
|
||||||
const btnH = BTN_SIZE;
|
|
||||||
|
|
||||||
// Button background
|
|
||||||
const btnBg = scene.add.graphics()
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(20001);
|
|
||||||
btnBg.fillStyle(0x1a2a1a, 0.9);
|
|
||||||
btnBg.fillRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
btnBg.lineStyle(1, 0x224422, 1.0);
|
|
||||||
btnBg.strokeRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
|
|
||||||
// Icon (preview of the building texture)
|
|
||||||
const icon = scene.add.image(
|
|
||||||
btnX + btnW / 2,
|
|
||||||
btnY + btnH / 2 - 8,
|
|
||||||
item.key
|
|
||||||
)
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(20002)
|
|
||||||
.setDisplaySize(btnW - 8, btnH - 20);
|
|
||||||
|
|
||||||
// Label text
|
|
||||||
const label = scene.add.text(
|
|
||||||
btnX + btnW / 2,
|
|
||||||
btnY + btnH - 10,
|
|
||||||
item.label,
|
|
||||||
{ fontSize: '9px', color: '#aaffcc', fontFamily: 'Arial', align: 'center' }
|
|
||||||
).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(20002);
|
|
||||||
|
|
||||||
// Invisible hit zone for click detection
|
|
||||||
const hitZone = scene.add.zone(btnX, btnY, btnW, btnH)
|
|
||||||
.setOrigin(0, 0)
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(20003)
|
|
||||||
.setInteractive({ cursor: 'pointer' });
|
|
||||||
|
|
||||||
hitZone.on('pointerover', () => {
|
|
||||||
btnBg.clear();
|
|
||||||
btnBg.fillStyle(0x2a4a2a, 1.0);
|
|
||||||
btnBg.fillRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
btnBg.lineStyle(2, 0x44ffaa, 1.0);
|
|
||||||
btnBg.strokeRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
});
|
|
||||||
|
|
||||||
hitZone.on('pointerout', () => {
|
|
||||||
const isSelected = this.selectedKey === item.key;
|
|
||||||
btnBg.clear();
|
|
||||||
btnBg.fillStyle(isSelected ? 0x1a4a1a : 0x1a2a1a, 0.9);
|
|
||||||
btnBg.fillRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
btnBg.lineStyle(isSelected ? 2 : 1, isSelected ? 0x44ffaa : 0x224422, 1.0);
|
|
||||||
btnBg.strokeRoundedRect(btnX, btnY, btnW, btnH, 6);
|
|
||||||
});
|
|
||||||
|
|
||||||
hitZone.on('pointerdown', () => {
|
|
||||||
this.selectBuilding(item.key);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store references to update highlight state
|
|
||||||
item._btnBg = btnBg;
|
|
||||||
item._btnX = btnX;
|
|
||||||
item._btnY = btnY;
|
|
||||||
item._btnW = btnW;
|
|
||||||
item._btnH = btnH;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// KEYBOARD: [B] toggles build mode
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
_setupInput() {
|
_setupInput() {
|
||||||
const scene = this.scene;
|
this.scene.input.keyboard.on('keydown-B', () => {
|
||||||
|
this.active ? this.deactivate() : this.activate();
|
||||||
// Toggle build mode with [B]
|
|
||||||
scene.input.keyboard.on('keydown-B', () => {
|
|
||||||
if (this.active) {
|
|
||||||
this.deactivate();
|
|
||||||
} else {
|
|
||||||
this.activate();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cancel / exit build mode with [Escape]
|
this.scene.input.keyboard.on('keydown-ESC', () => {
|
||||||
scene.input.keyboard.on('keydown-ESC', () => {
|
|
||||||
if (this.active) this.deactivate();
|
if (this.active) this.deactivate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// ACTIVATE BUILD MODE
|
// ACTIVATE
|
||||||
// ==========================================
|
// ==========================================
|
||||||
activate() {
|
activate() {
|
||||||
this.active = true;
|
this.active = true;
|
||||||
|
|
||||||
// If nothing selected yet, default to first item
|
|
||||||
if (!this.selectedKey) {
|
if (!this.selectedKey) {
|
||||||
this.selectBuilding(this.catalogue[0].key);
|
this.selectBuilding(this.catalogue[0].key);
|
||||||
} else {
|
} else {
|
||||||
this._createGhost(this.selectedKey);
|
this._createGhost(this.selectedKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show tip text
|
|
||||||
if (!this._modeTip) {
|
if (!this._modeTip) {
|
||||||
this._modeTip = this.scene.add.text(
|
this._modeTip = this.scene.add.text(
|
||||||
this.scene.cameras.main.width / 2,
|
this.scene.cameras.main.width / 2, 20,
|
||||||
20,
|
|
||||||
'🏗️ NAČIN GRADNJE | Klik = postavi | B / Esc = izhod',
|
'🏗️ NAČIN GRADNJE | Klik = postavi | B / Esc = izhod',
|
||||||
{
|
{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
@@ -235,32 +107,22 @@ export default class BuildingSystem {
|
|||||||
strokeThickness: 3,
|
strokeThickness: 3,
|
||||||
fontFamily: 'Arial',
|
fontFamily: 'Arial',
|
||||||
backgroundColor: '#0a0a0f99',
|
backgroundColor: '#0a0a0f99',
|
||||||
padding: { x: 10, y: 4 }
|
padding: { x: 10, y: 4 },
|
||||||
}
|
}
|
||||||
)
|
).setOrigin(0.5, 0).setScrollFactor(0).setDepth(20010);
|
||||||
.setOrigin(0.5, 0)
|
|
||||||
.setScrollFactor(0)
|
|
||||||
.setDepth(20010);
|
|
||||||
}
|
}
|
||||||
this._modeTip.setVisible(true);
|
this._modeTip.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// DEACTIVATE BUILD MODE
|
// DEACTIVATE
|
||||||
// ==========================================
|
// ==========================================
|
||||||
deactivate() {
|
deactivate() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.selectedKey = null;
|
this.selectedKey = null;
|
||||||
|
|
||||||
if (this.ghost) {
|
if (this.ghost) { this.ghost.destroy(); this.ghost = null; }
|
||||||
this.ghost.destroy();
|
|
||||||
this.ghost = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._modeTip) this._modeTip.setVisible(false);
|
if (this._modeTip) this._modeTip.setVisible(false);
|
||||||
|
|
||||||
// Reset all button highlights
|
|
||||||
this.catalogue.forEach(item => this._drawBtn(item, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -270,77 +132,49 @@ export default class BuildingSystem {
|
|||||||
this.selectedKey = key;
|
this.selectedKey = key;
|
||||||
if (!this.active) this.activate();
|
if (!this.active) this.activate();
|
||||||
|
|
||||||
// Destroy old ghost
|
if (this.ghost) { this.ghost.destroy(); this.ghost = null; }
|
||||||
if (this.ghost) {
|
|
||||||
this.ghost.destroy();
|
|
||||||
this.ghost = null;
|
|
||||||
}
|
|
||||||
this._createGhost(key);
|
this._createGhost(key);
|
||||||
|
|
||||||
// Update button highlights
|
|
||||||
this.catalogue.forEach(item => {
|
|
||||||
this._drawBtn(item, item.key === key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_drawBtn(item, selected) {
|
|
||||||
if (!item._btnBg) return;
|
|
||||||
item._btnBg.clear();
|
|
||||||
item._btnBg.fillStyle(selected ? 0x1a4a1a : 0x1a2a1a, 0.9);
|
|
||||||
item._btnBg.fillRoundedRect(item._btnX, item._btnY, item._btnW, item._btnH, 6);
|
|
||||||
item._btnBg.lineStyle(selected ? 2 : 1, selected ? 0x44ffaa : 0x224422, 1.0);
|
|
||||||
item._btnBg.strokeRoundedRect(item._btnX, item._btnY, item._btnW, item._btnH, 6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// GHOST PREVIEW SPRITE
|
// GHOST PREVIEW
|
||||||
// ==========================================
|
// ==========================================
|
||||||
_createGhost(key) {
|
_createGhost(key) {
|
||||||
const def = this.catalogue.find(c => c.key === key);
|
const def = this.catalogue.find(c => c.key === key);
|
||||||
if (!def) return;
|
if (!def) return;
|
||||||
|
|
||||||
const KAI_H = 64; // px
|
const KAI_H = 64;
|
||||||
const targetH = KAI_H * def.scale;
|
const targetH = KAI_H * def.scale;
|
||||||
const texture = this.scene.textures.get(key);
|
const srcH = this.scene.textures.get(key).getSourceImage().height;
|
||||||
const srcH = texture.getSourceImage().height;
|
|
||||||
const scale = targetH / srcH;
|
const scale = targetH / srcH;
|
||||||
|
|
||||||
this.ghost = this.scene.add.image(0, 0, key)
|
this.ghost = this.scene.add.image(0, 0, key)
|
||||||
.setScale(scale)
|
.setScale(scale)
|
||||||
.setOrigin(0.5, 1.0) // Feet at cursor
|
.setOrigin(0.5, 1.0)
|
||||||
.setAlpha(0.6)
|
.setAlpha(0.6)
|
||||||
.setTint(0x44ffaa) // Green tint = valid placement
|
.setTint(0x44ffaa)
|
||||||
.setDepth(99999);
|
.setDepth(99999);
|
||||||
|
|
||||||
// Make ghost follow pointer in update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// UPDATE (call from scene's update())
|
// UPDATE — klic iz scene.update()
|
||||||
// ==========================================
|
// ==========================================
|
||||||
update(pointer) {
|
update(pointer) {
|
||||||
if (!this.active || !this.ghost) return;
|
if (!this.active || !this.ghost) return;
|
||||||
|
|
||||||
// Ghost follows world-space cursor
|
const wx = pointer.worldX;
|
||||||
const worldX = pointer.worldX;
|
const wy = pointer.worldY;
|
||||||
const worldY = pointer.worldY;
|
this.ghost.setPosition(wx, wy);
|
||||||
this.ghost.setPosition(worldX, worldY);
|
|
||||||
|
|
||||||
// Check if placement is on the island (within physics bounds)
|
// Zelena = na otoku, Rdeča = zunaj
|
||||||
const bounds = this.scene.physics.world.bounds;
|
const b = this.scene.physics.world.bounds;
|
||||||
const onIsland = (
|
const ok = wx > b.x && wx < b.x + b.width &&
|
||||||
worldX > bounds.x &&
|
wy > b.y && wy < b.y + b.height;
|
||||||
worldX < bounds.x + bounds.width &&
|
this.ghost.setTint(ok ? 0x44ffaa : 0xff4444);
|
||||||
worldY > bounds.y &&
|
|
||||||
worldY < bounds.y + bounds.height
|
|
||||||
);
|
|
||||||
|
|
||||||
// Green = valid, Red = invalid
|
|
||||||
this.ghost.setTint(onIsland ? 0x44ffaa : 0xff4444);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// PLACE BUILDING (called on click)
|
// PLACE BUILDING (ob kliku)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
placeBuilding(worldX, worldY) {
|
placeBuilding(worldX, worldY) {
|
||||||
if (!this.active || !this.selectedKey) return;
|
if (!this.active || !this.selectedKey) return;
|
||||||
@@ -348,87 +182,73 @@ export default class BuildingSystem {
|
|||||||
const def = this.catalogue.find(c => c.key === this.selectedKey);
|
const def = this.catalogue.find(c => c.key === this.selectedKey);
|
||||||
if (!def) return;
|
if (!def) return;
|
||||||
|
|
||||||
// Validate placement is on island
|
// Validacija — mora biti na otoku
|
||||||
const bounds = this.scene.physics.world.bounds;
|
const b = this.scene.physics.world.bounds;
|
||||||
if (
|
if (worldX <= b.x || worldX >= b.x + b.width ||
|
||||||
worldX <= bounds.x || worldX >= bounds.x + bounds.width ||
|
worldY <= b.y || worldY >= b.y + b.height) {
|
||||||
worldY <= bounds.y || worldY >= bounds.y + bounds.height
|
// Flash rdeče
|
||||||
) {
|
if (this.ghost) {
|
||||||
// Flash ghost red
|
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this.ghost,
|
targets: this.ghost,
|
||||||
alpha: { from: 0.9, to: 0.3 },
|
alpha: { from: 0.9, to: 0.2 },
|
||||||
duration: 100,
|
duration: 80,
|
||||||
yoyo: true,
|
yoyo: true,
|
||||||
repeat: 2,
|
repeat: 2,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Calculate final scale ─────────────────────────────────────────
|
// Izračun scale
|
||||||
const KAI_H = 64;
|
const KAI_H = 64;
|
||||||
const targetH = KAI_H * def.scale;
|
const targetH = KAI_H * def.scale;
|
||||||
const texture = this.scene.textures.get(this.selectedKey);
|
const texture = this.scene.textures.get(this.selectedKey);
|
||||||
const srcH = texture.getSourceImage().height;
|
const srcH = texture.getSourceImage().height;
|
||||||
const scale = targetH / srcH;
|
const scale = targetH / srcH;
|
||||||
|
|
||||||
// ── Create placed sprite ──────────────────────────────────────────
|
// Ustvari static physics image
|
||||||
const building = this.scene.physics.add.staticImage(worldX, worldY, this.selectedKey)
|
const building = this.scene.physics.add.staticImage(worldX, worldY, this.selectedKey)
|
||||||
.setScale(scale)
|
.setScale(scale)
|
||||||
.setOrigin(0.5, 1.0);
|
.setOrigin(0.5, 1.0)
|
||||||
|
.setDepth(worldY);
|
||||||
|
|
||||||
// Y-sort: depth is based on the foot Y position (worldY) so Kai sorts properly
|
// Physics collider body (samo baza stavbe)
|
||||||
building.setDepth(worldY);
|
building.body.setSize(def.colliderW, def.colliderH);
|
||||||
|
|
||||||
// ── Physics collider body ─────────────────────────────────────────
|
|
||||||
// Adjust physics body to cover just the base of the building
|
|
||||||
const bodyW = def.colliderW;
|
|
||||||
const bodyH = def.colliderH;
|
|
||||||
const offsetX = srcH * scale * 0 + def.colliderOffsetX; // center-ish
|
|
||||||
const offsetY = def.colliderOffsetY; // above feet
|
|
||||||
building.body.setSize(bodyW, bodyH);
|
|
||||||
building.body.setOffset(
|
building.body.setOffset(
|
||||||
(texture.getSourceImage().width * scale) / 2 + def.colliderOffsetX,
|
(texture.getSourceImage().width * scale) / 2 + def.colliderOffsetX,
|
||||||
srcH * scale + def.colliderOffsetY
|
srcH * scale + def.colliderOffsetY
|
||||||
);
|
);
|
||||||
building.body.reset(worldX, worldY);
|
building.body.reset(worldX, worldY);
|
||||||
|
|
||||||
// ── Add to static group & collider ───────────────────────────────
|
// Collider med Kai in stavbo
|
||||||
this.buildingsGroup.add(building, true);
|
this.buildingsGroup.add(building, true);
|
||||||
this.scene.physics.add.collider(this.kai, building);
|
this.scene.physics.add.collider(this.kai, building);
|
||||||
|
|
||||||
// ── Spawn animation ───────────────────────────────────────────────
|
// Spawn animacija
|
||||||
building.setAlpha(0);
|
building.setAlpha(0);
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: building,
|
targets: building,
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
scaleX: scale,
|
duration: 280,
|
||||||
scaleY: scale,
|
|
||||||
duration: 300,
|
|
||||||
ease: 'Back.out',
|
ease: 'Back.out',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save for later
|
// Flash feedback
|
||||||
this.placed.push({ key: this.selectedKey, x: worldX, y: worldY });
|
const flash = this.scene.add.rectangle(worldX, worldY - targetH * 0.5, 80, 80, 0x44ffaa, 0.6)
|
||||||
|
|
||||||
// ── Update depth every frame (for Y-sort) ────────────────────────
|
|
||||||
// Store ref so update() can sort depths
|
|
||||||
building._isBuilding = true;
|
|
||||||
this.scene._buildingSprites = this.scene._buildingSprites || [];
|
|
||||||
this.scene._buildingSprites.push(building);
|
|
||||||
|
|
||||||
// Small flash feedback
|
|
||||||
const flash = this.scene.add.rectangle(worldX, worldY - targetH * 0.5, 80, 80, 0x44ffaa, 0.7)
|
|
||||||
.setDepth(99998);
|
.setDepth(99998);
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: flash,
|
targets: flash,
|
||||||
alpha: 0,
|
alpha: 0, scaleX: 2, scaleY: 2,
|
||||||
scaleX: 2,
|
duration: 350,
|
||||||
scaleY: 2,
|
|
||||||
duration: 400,
|
|
||||||
onComplete: () => flash.destroy(),
|
onComplete: () => flash.destroy(),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`🏗️ Placed: ${this.selectedKey} at (${Math.round(worldX)}, ${Math.round(worldY)})`);
|
// Shrani referenco za Y-sort v update()
|
||||||
|
this.scene._buildingSprites = this.scene._buildingSprites || [];
|
||||||
|
this.scene._buildingSprites.push(building);
|
||||||
|
|
||||||
|
this.placed.push({ key: this.selectedKey, x: worldX, y: worldY });
|
||||||
|
|
||||||
|
console.log(`🏗️ Placed: ${this.selectedKey} @ (${Math.round(worldX)}, ${Math.round(worldY)})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
441
nova farma TRAE/src/systems/DayNightSystem.js
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* DayNightSystem.js — Nova Farma
|
||||||
|
* ============================================================
|
||||||
|
* Realen dan/noč cikel z vsemi hook-i:
|
||||||
|
*
|
||||||
|
* - Vsaka sekunda realna = 1 minuta v igri (24 min real = 1 dan)
|
||||||
|
* - 06:00 = Jutro (svetlo, ptice)
|
||||||
|
* - 12:00 = Poldne (svetlo max)
|
||||||
|
* - 20:00 = Večer (tema pada, oranžna)
|
||||||
|
* - 22:00 = Noč (modra tema, zvezde)
|
||||||
|
* - 00:00 = Polnoč → nov dan trigger
|
||||||
|
*
|
||||||
|
* Events emittirani:
|
||||||
|
* - 'dayChanged' { day } → FarmingSystem.onNewDay(day)
|
||||||
|
* - 'morningArrived' { day } → Budnica / ptice / energija reset
|
||||||
|
* - 'nightArrived' { day } → Zombiji aktiv
|
||||||
|
* - 'hourChanged' { hour, minute } → UIScene clock update
|
||||||
|
*
|
||||||
|
* Spanje:
|
||||||
|
* - [Z] blizu spalne vreče/šotora → Sleep → skok na 06:00 naslednjega dne
|
||||||
|
* ============================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class DayNightSystem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Phaser.Scene} scene
|
||||||
|
* @param {object} options
|
||||||
|
*/
|
||||||
|
constructor(scene, options = {}) {
|
||||||
|
this.scene = scene;
|
||||||
|
|
||||||
|
// Čas
|
||||||
|
this.day = 1;
|
||||||
|
this.hour = 6; // Začne ob 6:00
|
||||||
|
this.minute = 0;
|
||||||
|
|
||||||
|
// Hitrost: 1 real sekunda = N minut v igri
|
||||||
|
// Default: 24 real minut = 1 dan → 1 real sec = 1 igra minuta
|
||||||
|
this.gameMinutesPerRealSecond = options.speed || 1;
|
||||||
|
|
||||||
|
// Interaktivni objekti za spanje
|
||||||
|
this.sleepObjects = []; // { x, y, sprite, type }
|
||||||
|
this.isSleeping = false;
|
||||||
|
this.interactRange = 90;
|
||||||
|
|
||||||
|
// Input [Z] za spanje
|
||||||
|
this.keyZ = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z);
|
||||||
|
|
||||||
|
// Ambient Graphics (overlay za noč)
|
||||||
|
this.ambientOverlay = scene.add.graphics()
|
||||||
|
.setScrollFactor(0)
|
||||||
|
.setDepth(4500); // Pod UIScene (depth 5000+), nad svetom
|
||||||
|
|
||||||
|
// Star particles za noč
|
||||||
|
this.stars = [];
|
||||||
|
this._initStars();
|
||||||
|
|
||||||
|
// Čas tracking
|
||||||
|
this._accumMs = 0;
|
||||||
|
|
||||||
|
// Callback cache
|
||||||
|
this._lastHour = -1;
|
||||||
|
this._lastDay = -1;
|
||||||
|
|
||||||
|
// Sleep prompt text
|
||||||
|
this._sleepPrompt = null;
|
||||||
|
this._highlightGfx = scene.add.graphics().setDepth(9002);
|
||||||
|
|
||||||
|
console.log(`🌅 DayNightSystem initialized — Dan ${this.day}, ${this._timeStr()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UPDATE — Kliči iz scene.update() z delta
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
update(delta, kai) {
|
||||||
|
if (this.isSleeping) return;
|
||||||
|
|
||||||
|
// ── Tick čas ──
|
||||||
|
this._accumMs += delta;
|
||||||
|
const msPerGameMin = 1000 / this.gameMinutesPerRealSecond;
|
||||||
|
|
||||||
|
while (this._accumMs >= msPerGameMin) {
|
||||||
|
this._accumMs -= msPerGameMin;
|
||||||
|
this._tickMinute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ambient svetloba ──
|
||||||
|
this._updateAmbient();
|
||||||
|
|
||||||
|
// ── Zvezde ──
|
||||||
|
this._updateStars();
|
||||||
|
|
||||||
|
// ── Sleep interakcija ──
|
||||||
|
this._highlightGfx.clear();
|
||||||
|
const nearSleep = this._getSleepNear(kai);
|
||||||
|
if (nearSleep) {
|
||||||
|
this._highlightGfx.lineStyle(2, 0xaaaaff, 0.8);
|
||||||
|
this._highlightGfx.strokeCircle(nearSleep.x, nearSleep.y, 50);
|
||||||
|
this._showSleepPrompt(nearSleep);
|
||||||
|
} else {
|
||||||
|
this._hideSleepPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Phaser.Input.Keyboard.JustDown(this.keyZ)) {
|
||||||
|
if (nearSleep) {
|
||||||
|
this.sleep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// TICK minuta
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_tickMinute() {
|
||||||
|
this.minute++;
|
||||||
|
if (this.minute >= 60) {
|
||||||
|
this.minute = 0;
|
||||||
|
this.hour++;
|
||||||
|
this._onHourChange();
|
||||||
|
}
|
||||||
|
if (this.hour >= 24) {
|
||||||
|
this.hour = 0;
|
||||||
|
this._onMidnight();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit UI update vsako minuto
|
||||||
|
this.scene.events.emit('timeChanged', {
|
||||||
|
day: this.day,
|
||||||
|
hour: this.hour,
|
||||||
|
minute: this.minute,
|
||||||
|
timeStr: this._timeStr(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// HOOK — Vsaka ura
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_onHourChange() {
|
||||||
|
this.scene.events.emit('hourChanged', {
|
||||||
|
day: this.day,
|
||||||
|
hour: this.hour,
|
||||||
|
minute: this.minute,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Posebni uri:
|
||||||
|
if (this.hour === 6) {
|
||||||
|
// Jutro!
|
||||||
|
this.scene.events.emit('morningArrived', { day: this.day });
|
||||||
|
console.log(`🌅 Jutro! Dan ${this.day}, 06:00`);
|
||||||
|
}
|
||||||
|
if (this.hour === 20) {
|
||||||
|
// Večer
|
||||||
|
this.scene.events.emit('eveningArrived', { day: this.day });
|
||||||
|
console.log(`🌆 Večer — zombiji se bujajo...`);
|
||||||
|
}
|
||||||
|
if (this.hour === 22) {
|
||||||
|
// Noč
|
||||||
|
this.scene.events.emit('nightArrived', { day: this.day });
|
||||||
|
console.log(`🌙 Noč! Be careful, Kai...`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// HOOK — Polnoč → Nov dan
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_onMidnight() {
|
||||||
|
this.day++;
|
||||||
|
console.log(`🌙 Polnoč → Dan ${this.day}!`);
|
||||||
|
|
||||||
|
// ── Farming tick ──
|
||||||
|
this.scene.events.emit('dayChanged', { day: this.day });
|
||||||
|
|
||||||
|
// ── Rain check (30% chance) ──
|
||||||
|
if (Math.random() < 0.3) {
|
||||||
|
this.scene.events.emit('rainDay', { day: this.day });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// SPANJE — Skip na 06:00 naslednjega dne
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
sleep() {
|
||||||
|
if (this.isSleeping) return;
|
||||||
|
// Dovoli spanje samo med 20:00 in 24:00 ali 00:00-06:00
|
||||||
|
if (this.hour >= 6 && this.hour < 20) {
|
||||||
|
this._showFloatingText(
|
||||||
|
this.scene.cameras.main.width / 2,
|
||||||
|
this.scene.cameras.main.height / 2 - 60,
|
||||||
|
'😴 Preuranjeno za spati! (po 20:00)',
|
||||||
|
'#ffaa44', 20, true
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSleeping = true;
|
||||||
|
console.log(`😴 Kai gre spat... (${this._timeStr()} → 06:00)`);
|
||||||
|
|
||||||
|
// Fade black screen
|
||||||
|
this.scene.cameras.main.fadeOut(800, 0, 0, 0);
|
||||||
|
|
||||||
|
this.scene.time.delayedCall(900, () => {
|
||||||
|
// Preskoči na jutro
|
||||||
|
const prevDay = this.day;
|
||||||
|
if (this.hour >= 20 || this.hour < 6) {
|
||||||
|
this._onMidnight(); // Nov dan
|
||||||
|
}
|
||||||
|
this.hour = 6;
|
||||||
|
this.minute = 0;
|
||||||
|
|
||||||
|
// Emit morning events
|
||||||
|
this.scene.events.emit('morningArrived', { day: this.day });
|
||||||
|
this.scene.events.emit('timeChanged', {
|
||||||
|
day: this.day, hour: 6, minute: 0, timeStr: '06:00'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fade back in
|
||||||
|
this.scene.cameras.main.fadeIn(1200, 0, 0, 0);
|
||||||
|
|
||||||
|
// Prikaži "Dan X" overlay
|
||||||
|
this._showDayOverlay(this.day);
|
||||||
|
|
||||||
|
this.scene.time.delayedCall(1300, () => {
|
||||||
|
this.isSleeping = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// AMBIENT OSVETLITEV
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_updateAmbient() {
|
||||||
|
const W = this.scene.cameras.main.width;
|
||||||
|
const H = this.scene.cameras.main.height;
|
||||||
|
const h = this.hour + this.minute / 60;
|
||||||
|
|
||||||
|
let alpha = 0;
|
||||||
|
let r = 0, g = 0, b = 0;
|
||||||
|
|
||||||
|
if (h >= 6 && h < 8) {
|
||||||
|
// Zora: oranžno-roza tema
|
||||||
|
const t = (h - 6) / 2;
|
||||||
|
alpha = 0.25 * (1 - t);
|
||||||
|
r = 0xff; g = 0x88; b = 0x00;
|
||||||
|
} else if (h >= 8 && h < 18) {
|
||||||
|
// Dan: brez overlaya
|
||||||
|
alpha = 0;
|
||||||
|
} else if (h >= 18 && h < 20) {
|
||||||
|
// Sončni zahod: oranžna
|
||||||
|
const t = (h - 18) / 2;
|
||||||
|
alpha = 0.15 + t * 0.2;
|
||||||
|
r = 0xff; g = 0x66; b = 0x00;
|
||||||
|
} else if (h >= 20 && h < 22) {
|
||||||
|
// Večer: temno modra
|
||||||
|
const t = (h - 20) / 2;
|
||||||
|
alpha = 0.35 + t * 0.25;
|
||||||
|
r = 0x00; g = 0x00; b = 0x44;
|
||||||
|
} else if (h >= 22 || h < 4) {
|
||||||
|
// Noč: globoka modra tema
|
||||||
|
alpha = 0.60;
|
||||||
|
r = 0x00; g = 0x00; b = 0x22;
|
||||||
|
} else if (h >= 4 && h < 6) {
|
||||||
|
// Pred zoro
|
||||||
|
const t = (h - 4) / 2;
|
||||||
|
alpha = 0.60 * (1 - t * 0.5);
|
||||||
|
r = 0x00; g = 0x00; b = 0x22;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ambientOverlay.clear();
|
||||||
|
if (alpha > 0.01) {
|
||||||
|
this.ambientOverlay.fillStyle(
|
||||||
|
(r << 16) | (g << 8) | b,
|
||||||
|
alpha
|
||||||
|
);
|
||||||
|
this.ambientOverlay.fillRect(0, 0, W, H);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// ZVEZDE (Noč)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_initStars() {
|
||||||
|
const W = this.scene.cameras.main.width;
|
||||||
|
const H = this.scene.cameras.main.height;
|
||||||
|
this.starGfx = this.scene.add.graphics()
|
||||||
|
.setScrollFactor(0)
|
||||||
|
.setDepth(4499);
|
||||||
|
|
||||||
|
for (let i = 0; i < 80; i++) {
|
||||||
|
this.stars.push({
|
||||||
|
x: Math.random() * W,
|
||||||
|
y: Math.random() * H * 0.6, // Samo zgoraj
|
||||||
|
size: 0.5 + Math.random() * 1.5,
|
||||||
|
twinkle: Math.random() * Math.PI * 2,
|
||||||
|
speed: 0.5 + Math.random() * 1.5,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateStars() {
|
||||||
|
const h = this.hour + this.minute / 60;
|
||||||
|
// Stars visible 21:00 - 05:00
|
||||||
|
let starAlpha = 0;
|
||||||
|
if (h >= 21 || h < 5) {
|
||||||
|
starAlpha = (h >= 21) ? Math.min(1, (h - 21) / 0.5) : Math.min(1, (5 - h) / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.starGfx.clear();
|
||||||
|
if (starAlpha < 0.01) return;
|
||||||
|
|
||||||
|
const t = Date.now() * 0.001;
|
||||||
|
for (const star of this.stars) {
|
||||||
|
const twinkle = 0.4 + 0.6 * Math.sin(t * star.speed + star.twinkle);
|
||||||
|
this.starGfx.fillStyle(0xffffff, starAlpha * twinkle);
|
||||||
|
this.starGfx.fillCircle(star.x, star.y, star.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// SLEEP OBJECTS MANAGEMENT
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
registerSleepObject(x, y, sprite, type = 'sleeping_bag') {
|
||||||
|
this.sleepObjects.push({ x, y, sprite, type });
|
||||||
|
console.log(`😴 Sleep object registered: ${type} @ ${x},${y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getSleepNear(kai) {
|
||||||
|
let closest = null;
|
||||||
|
let minDist = this.interactRange;
|
||||||
|
for (const obj of this.sleepObjects) {
|
||||||
|
const d = Phaser.Math.Distance.Between(kai.x, kai.y, obj.x, obj.y);
|
||||||
|
if (d < minDist) { minDist = d; closest = obj; }
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UI HELPERS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_timeStr() {
|
||||||
|
return `${String(this.hour).padStart(2, '0')}:${String(this.minute).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showSleepPrompt(obj) {
|
||||||
|
if (!this._sleepPrompt) {
|
||||||
|
this._sleepPrompt = this.scene.add.text(0, 0, '', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#aaaaff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
backgroundColor: '#00000099',
|
||||||
|
padding: { x: 8, y: 4 },
|
||||||
|
}).setDepth(9999).setScrollFactor(1);
|
||||||
|
}
|
||||||
|
const label = (this.hour >= 20 || this.hour < 6)
|
||||||
|
? `😴 [Z] Pojdi spat`
|
||||||
|
: `🌅 [Z] Spanje (po 20:00)`;
|
||||||
|
this._sleepPrompt.setText(label).setAlpha(1).setPosition(obj.x - 60, obj.y - 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideSleepPrompt() {
|
||||||
|
if (this._sleepPrompt) this._sleepPrompt.setAlpha(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showDayOverlay(day) {
|
||||||
|
const W = this.scene.cameras.main.width;
|
||||||
|
const H = this.scene.cameras.main.height;
|
||||||
|
|
||||||
|
const txt = this.scene.add.text(W / 2, H / 2, `☀️ DAN ${day}`, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '64px',
|
||||||
|
color: '#ffdd44',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 8,
|
||||||
|
}).setOrigin(0.5).setScrollFactor(0).setDepth(6000).setAlpha(0);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
alpha: { from: 0, to: 1 },
|
||||||
|
y: H / 2 - 20,
|
||||||
|
duration: 600,
|
||||||
|
ease: 'Back.out',
|
||||||
|
onComplete: () => {
|
||||||
|
this.scene.time.delayedCall(1200, () => {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
alpha: 0,
|
||||||
|
y: H / 2 - 60,
|
||||||
|
duration: 500,
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_showFloatingText(x, y, msg, color, size = 20, fixedToCamera = false) {
|
||||||
|
const txt = this.scene.add.text(x, y, msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: `${size}px`,
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5).setDepth(9998);
|
||||||
|
if (fixedToCamera) txt.setScrollFactor(0);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
y: y - 50,
|
||||||
|
alpha: { from: 1, to: 0 },
|
||||||
|
duration: 2000,
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PUBLIC API
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
getTimeStr() { return this._timeStr(); }
|
||||||
|
getDay() { return this.day; }
|
||||||
|
getHour() { return this.hour; }
|
||||||
|
isNight() { return this.hour >= 22 || this.hour < 6; }
|
||||||
|
isDay() { return this.hour >= 6 && this.hour < 20; }
|
||||||
|
|
||||||
|
/** Nastavi čas (za debug/testing) */
|
||||||
|
setTime(hour, minute = 0) {
|
||||||
|
this.hour = hour;
|
||||||
|
this.minute = minute;
|
||||||
|
console.log(`⏰ Čas nastavljen: ${this._timeStr()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.ambientOverlay?.destroy();
|
||||||
|
this.starGfx?.destroy();
|
||||||
|
this.starGfx = null;
|
||||||
|
this._sleepPrompt?.destroy();
|
||||||
|
this._highlightGfx?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
660
nova farma TRAE/src/systems/FarmingSystem.js
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* FarmingSystem.js — Nova Farma
|
||||||
|
* ============================================================
|
||||||
|
* Farming logika:
|
||||||
|
* - [E] blizu tal → Ore (Hoe) → tilled_soil tile
|
||||||
|
* - [E] blizu tilled tile (z semenom v roki) → Posadi
|
||||||
|
* - [E] blizu rastline (z zalivalko v roki) → Zalij
|
||||||
|
* - Rast po N dnevih (tick ob sleep/new day)
|
||||||
|
* - [E] blizu zrele rastline → Harvest → item v inventar
|
||||||
|
* - Brez zalivanja 1 dan → rastlina uvene → izguba
|
||||||
|
* ============================================================
|
||||||
|
*
|
||||||
|
* CROP DEFINITIONS:
|
||||||
|
* wheat: 4 stopnje, 3 dni, 30g semena, 3×30g harvest
|
||||||
|
* carrot: 5 stopenj, 4 dni, 15g semena, 2×40g harvest
|
||||||
|
* cannabis: 6 stopenj, 7 dni, 50g semena, 4×200g harvest (Faza 1)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class FarmingSystem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Phaser.Scene} scene — GrassSceneClean instanca
|
||||||
|
* @param {object} options — { islandX, islandY, tileSize }
|
||||||
|
*/
|
||||||
|
constructor(scene, options = {}) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.islandX = options.islandX || 2000;
|
||||||
|
this.islandY = options.islandY || 2000;
|
||||||
|
this.tileSize = options.tileSize || 128;
|
||||||
|
|
||||||
|
// Crop definicije
|
||||||
|
this.CROPS = {
|
||||||
|
wheat: {
|
||||||
|
key: 'wheat',
|
||||||
|
stages: 4, // 0=sprout,1=small,2=medium,3=ripe
|
||||||
|
daysToGrow: 3, // Dan od saditve do žetve
|
||||||
|
seedCost: 10,
|
||||||
|
harvestItem: 'wheat',
|
||||||
|
harvestCount: [1, 3], // min,max
|
||||||
|
harvestValue: 30,
|
||||||
|
stageKeys: [
|
||||||
|
'crop_wheat_stage0',
|
||||||
|
'crop_wheat_stage1',
|
||||||
|
'crop_wheat_stage2',
|
||||||
|
'crop_wheat_stage3',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
carrot: {
|
||||||
|
key: 'carrot',
|
||||||
|
stages: 5,
|
||||||
|
daysToGrow: 4,
|
||||||
|
seedCost: 15,
|
||||||
|
harvestItem: 'carrot',
|
||||||
|
harvestCount: [1, 2],
|
||||||
|
harvestValue: 40,
|
||||||
|
stageKeys: [
|
||||||
|
'crop_carrot_stage0',
|
||||||
|
'crop_carrot_stage1',
|
||||||
|
'crop_carrot_stage2',
|
||||||
|
'crop_carrot_stage3',
|
||||||
|
'crop_carrot_stage4',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cannabis: {
|
||||||
|
key: 'cannabis',
|
||||||
|
stages: 6,
|
||||||
|
daysToGrow: 7,
|
||||||
|
seedCost: 50,
|
||||||
|
harvestItem: 'cannabis_bud',
|
||||||
|
harvestCount: [3, 5],
|
||||||
|
harvestValue: 200,
|
||||||
|
stageKeys: [
|
||||||
|
'crop_cannabis_stage0',
|
||||||
|
'crop_cannabis_stage1',
|
||||||
|
'crop_cannabis_stage2',
|
||||||
|
'crop_cannabis_stage3',
|
||||||
|
'crop_cannabis_stage4',
|
||||||
|
'crop_cannabis_stage5',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stanje
|
||||||
|
this.tilledPlots = []; // { x, y, tileSprite, crop, dayPlanted, dayWatered, stage, sprite, wilted }
|
||||||
|
this.currentDay = 1;
|
||||||
|
this.interactRange = 60; // px — razdalja za interakcijo
|
||||||
|
this.TILE_DISPLAY = 40; // Vizualna velikost tilled tile — majhna kocka!
|
||||||
|
|
||||||
|
// Aktiven tool v roki (set from InventorySystem)
|
||||||
|
this.activeTool = 'hoe'; // 'hoe' | 'watering_can' | null
|
||||||
|
this.activeSeed = 'wheat'; // 'wheat' | 'carrot' | 'cannabis'
|
||||||
|
|
||||||
|
// Physics group za tilled tile colliderje
|
||||||
|
this.plotGroup = scene.physics.add.staticGroup();
|
||||||
|
|
||||||
|
// Graphics za highlight (Kai blizu)
|
||||||
|
this.highlightGfx = scene.add.graphics().setDepth(9000);
|
||||||
|
|
||||||
|
// Input — E tipka
|
||||||
|
this.keyE = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E);
|
||||||
|
|
||||||
|
// Debounce
|
||||||
|
this._lastInteract = 0;
|
||||||
|
|
||||||
|
console.log('🌱 FarmingSystem initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PRELOAD — Assets (kliči iz scene.preload PRED new FarmingSystem)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
static preload(scene) {
|
||||||
|
// Tilled soil
|
||||||
|
scene.load.image('tilled_soil', 'DEMO_FAZA1/Crops/tilled_soil.png');
|
||||||
|
|
||||||
|
// Wheat (4 stopnje)
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
scene.load.image(`crop_wheat_stage${i}`, `DEMO_FAZA1/Crops/crop_wheat_stage${i}.png`);
|
||||||
|
}
|
||||||
|
// Carrot (5 stopenj)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
scene.load.image(`crop_carrot_stage${i}`, `DEMO_FAZA1/Crops/crop_carrot_stage${i}.png`);
|
||||||
|
}
|
||||||
|
// Cannabis (6 stopenj)
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
scene.load.image(`crop_cannabis_stage${i}`, `DEMO_FAZA1/Crops/crop_cannabis_stage${i}.png`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UPDATE — Kliči iz scene.update()
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
update(kai, time) {
|
||||||
|
this.highlightGfx.clear();
|
||||||
|
|
||||||
|
const nearPlot = this._getPlotNear(kai);
|
||||||
|
const nearEmpty = this._getEmptyGroundNear(kai);
|
||||||
|
|
||||||
|
// ── Highlight najbližji plot ──
|
||||||
|
const W = 48, H = 24; // Velikost tilled plot kocke
|
||||||
|
if (nearPlot) {
|
||||||
|
this.highlightGfx.lineStyle(2, 0x44ffcc, 0.9);
|
||||||
|
this.highlightGfx.strokeRect(
|
||||||
|
nearPlot.x - W / 2,
|
||||||
|
nearPlot.y - H / 2,
|
||||||
|
W, H
|
||||||
|
);
|
||||||
|
// Tooltip nad plotom
|
||||||
|
this._drawTooltip(nearPlot);
|
||||||
|
} else if (nearEmpty && this.activeTool === 'hoe') {
|
||||||
|
// Pregled oranja
|
||||||
|
this.highlightGfx.lineStyle(2, 0xaabb88, 0.5);
|
||||||
|
this.highlightGfx.strokeRect(
|
||||||
|
nearEmpty.x - W / 2,
|
||||||
|
nearEmpty.y - H / 2,
|
||||||
|
W, H
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── E tipka — interakcija ──
|
||||||
|
if (Phaser.Input.Keyboard.JustDown(this.keyE)) {
|
||||||
|
if (nearPlot) {
|
||||||
|
this._interactWithPlot(nearPlot, kai);
|
||||||
|
} else if (nearEmpty && this.activeTool === 'hoe') {
|
||||||
|
this._tillGround(nearEmpty.x, nearEmpty.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// ORE (TILL) — Ustvari nov tilled plot
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_tillGround(x, y) {
|
||||||
|
// Snap na grid — center tile-a
|
||||||
|
const ts = this.tileSize;
|
||||||
|
const snappedX = Math.floor(x / ts) * ts + ts / 2;
|
||||||
|
const snappedY = Math.floor(y / ts) * ts + ts / 2;
|
||||||
|
|
||||||
|
// Ali plot že obstaja?
|
||||||
|
if (this.tilledPlots.some(p => Math.abs(p.x - snappedX) < 4 && Math.abs(p.y - snappedY) < 4)) return;
|
||||||
|
|
||||||
|
// === Nariši tilled soil z Graphics — točna velikost, brez skaliranja slik ===
|
||||||
|
const W = 48; // širina kocke (world px)
|
||||||
|
const H = 24; // višina kocke (world px)
|
||||||
|
const gfx = this.scene.add.graphics();
|
||||||
|
gfx.setDepth(snappedY - 50);
|
||||||
|
|
||||||
|
// Ozadje — temna rjava zemlja
|
||||||
|
gfx.fillStyle(0x3a2010, 1);
|
||||||
|
gfx.fillRect(snappedX - W / 2, snappedY - H / 2, W, H);
|
||||||
|
|
||||||
|
// Brazde (bela linija × 3, horizontalne)
|
||||||
|
gfx.lineStyle(1, 0x6b4020, 0.7);
|
||||||
|
const lines = 3;
|
||||||
|
for (let i = 1; i <= lines; i++) {
|
||||||
|
const ly = snappedY - H / 2 + (H / (lines + 1)) * i;
|
||||||
|
gfx.beginPath();
|
||||||
|
gfx.moveTo(snappedX - W / 2 + 3, ly);
|
||||||
|
gfx.lineTo(snappedX + W / 2 - 3, ly);
|
||||||
|
gfx.strokePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tanka okvirna linija
|
||||||
|
gfx.lineStyle(1, 0x8b5a2b, 0.5);
|
||||||
|
gfx.strokeRect(snappedX - W / 2, snappedY - H / 2, W, H);
|
||||||
|
|
||||||
|
// Pop-in animacija — skali iz 0
|
||||||
|
gfx.setScale(0.1);
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: gfx,
|
||||||
|
scaleX: 1.0,
|
||||||
|
scaleY: 1.0,
|
||||||
|
duration: 250,
|
||||||
|
ease: 'Back.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shrani referenčni objekt (tile = gfx)
|
||||||
|
const tile = gfx;
|
||||||
|
|
||||||
|
// Physics static body za collider
|
||||||
|
const body = this.plotGroup.create(snappedX, snappedY);
|
||||||
|
body.setDisplaySize(this.tileSize - 4, this.tileSize - 4);
|
||||||
|
body.setAlpha(0);
|
||||||
|
body.refreshBody();
|
||||||
|
|
||||||
|
// Shrani plot
|
||||||
|
const plot = {
|
||||||
|
x: snappedX,
|
||||||
|
y: snappedY,
|
||||||
|
tileSprite: tile,
|
||||||
|
bodyRef: body,
|
||||||
|
crop: null,
|
||||||
|
dayPlanted: null,
|
||||||
|
dayWatered: null,
|
||||||
|
stage: 0,
|
||||||
|
sprite: null,
|
||||||
|
wilted: false,
|
||||||
|
};
|
||||||
|
this.tilledPlots.push(plot);
|
||||||
|
|
||||||
|
// Animacija: "Pop" ob oranju
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: tile,
|
||||||
|
scaleX: { from: 1.1, to: 1.0 },
|
||||||
|
scaleY: { from: 0.8, to: 1.0 },
|
||||||
|
duration: 200,
|
||||||
|
ease: 'Bounce.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
this._showFloatingText(snappedX, snappedY, '🪱 Poorano!', '#aabb88');
|
||||||
|
console.log(`🌱 Poorano: ${snappedX}, ${snappedY}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// INTERAKCIJA Z PLOTOM (posadi / zalij / poženi)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_interactWithPlot(plot, kai) {
|
||||||
|
if (plot.wilted) {
|
||||||
|
// Uvela rastlina — odstrani in reciklira plot
|
||||||
|
this._removeCrop(plot);
|
||||||
|
this._showFloatingText(plot.x, plot.y, '🍂 Odstranjeno', '#cc6666');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plot.crop) {
|
||||||
|
// 1. Prazno poorano → Posadi
|
||||||
|
if (this.activeTool === 'hoe' || this.activeSeed) {
|
||||||
|
this._plantCrop(plot, this.activeSeed);
|
||||||
|
}
|
||||||
|
} else if (plot.crop && plot.stage < this._getCropDef(plot.crop).stages - 1) {
|
||||||
|
// 2. Rastlina raste → Zalij (samo z zalivalko)
|
||||||
|
if (this.activeTool === 'watering_can') {
|
||||||
|
this._waterCrop(plot);
|
||||||
|
} else {
|
||||||
|
this._showFloatingText(plot.x, plot.y - 30, '💧 Vzemi zalivalko!', '#aaaaff');
|
||||||
|
}
|
||||||
|
} else if (plot.stage >= this._getCropDef(plot.crop).stages - 1 && !plot.wilted) {
|
||||||
|
// 3. Zrela → Poženi
|
||||||
|
this._harvestCrop(plot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// POSADI
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_plantCrop(plot, cropKey) {
|
||||||
|
const def = this._getCropDef(cropKey);
|
||||||
|
if (!def) {
|
||||||
|
console.warn('Unknown crop:', cropKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plot.crop = cropKey;
|
||||||
|
plot.dayPlanted = this.currentDay;
|
||||||
|
plot.dayWatered = this.currentDay; // Zaliješ takoj ob sajenju
|
||||||
|
plot.stage = 0;
|
||||||
|
plot.wilted = false;
|
||||||
|
|
||||||
|
// Ustvari sprite za rastlino (stage 0)
|
||||||
|
// Stage 0 = 20px visoka (klica), max stage = 70px (zrela rastlina)
|
||||||
|
const stageKey = def.stageKeys[0];
|
||||||
|
const cropH = 20;
|
||||||
|
const texture = this.scene.textures.get(stageKey);
|
||||||
|
const srcH = texture?.getSourceImage()?.height || 100;
|
||||||
|
const scale = cropH / srcH;
|
||||||
|
|
||||||
|
// Posadi na center tilled tile-a z offset za vizualni izgled
|
||||||
|
plot.sprite = this.scene.add.image(plot.x, plot.y, stageKey)
|
||||||
|
.setScale(scale)
|
||||||
|
.setOrigin(0.5, 1.0)
|
||||||
|
.setDepth(plot.y + 1)
|
||||||
|
.setAlpha(0);
|
||||||
|
|
||||||
|
// Pop-in animacija
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: plot.sprite,
|
||||||
|
alpha: 1,
|
||||||
|
scaleX: { from: scale * 1.4, to: scale },
|
||||||
|
scaleY: { from: scale * 0.5, to: scale },
|
||||||
|
duration: 350,
|
||||||
|
ease: 'Back.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
this._showFloatingText(plot.x, plot.y - 30, `🌱 ${cropKey}!`, '#88ff88', 18);
|
||||||
|
console.log(`🌱 Posajeno: ${cropKey} @ day ${this.currentDay}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// ZALIJ
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_waterCrop(plot) {
|
||||||
|
if (!plot.crop) return;
|
||||||
|
if (plot.dayWatered === this.currentDay) {
|
||||||
|
this._showFloatingText(plot.x, plot.y - 30, '✅ Že zalivano!', '#44ffcc');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === PREVERI VODO v vedru ===
|
||||||
|
if (this.waterSystem) {
|
||||||
|
if (!this.waterSystem.hasWater(1)) {
|
||||||
|
this._showFloatingText(plot.x, plot.y - 30,
|
||||||
|
'❌ Vedro prazno! [F] pri RC', '#ff6666', 16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.waterSystem.useWater(1); // Porabi 1L
|
||||||
|
}
|
||||||
|
|
||||||
|
plot.dayWatered = this.currentDay;
|
||||||
|
plot.wilted = false;
|
||||||
|
|
||||||
|
// Ripple efekt — modri krogci
|
||||||
|
this._waterRipple(plot.x, plot.y);
|
||||||
|
this._showFloatingText(plot.x, plot.y - 20, '💧 Zalivano!', '#44aaff');
|
||||||
|
|
||||||
|
// Tla (Graphics) - prebarva temnejše ko so mokra
|
||||||
|
if (plot.tileSprite) {
|
||||||
|
plot.tileSprite.clear();
|
||||||
|
const W = 48, H = 24;
|
||||||
|
const snappedX = plot.x, snappedY = plot.y;
|
||||||
|
// Mokra zemlja = temnejša
|
||||||
|
plot.tileSprite.fillStyle(0x25140a, 1);
|
||||||
|
plot.tileSprite.fillRect(snappedX - W / 2, snappedY - H / 2, W, H);
|
||||||
|
plot.tileSprite.lineStyle(1, 0x4a2c10, 0.7);
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const ly = snappedY - H / 2 + (H / 4) * i;
|
||||||
|
plot.tileSprite.beginPath();
|
||||||
|
plot.tileSprite.moveTo(snappedX - W / 2 + 3, ly);
|
||||||
|
plot.tileSprite.lineTo(snappedX + W / 2 - 3, ly);
|
||||||
|
plot.tileSprite.strokePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// ŽETEV
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_harvestCrop(plot) {
|
||||||
|
const def = this._getCropDef(plot.crop);
|
||||||
|
const count = Phaser.Math.Between(def.harvestCount[0], def.harvestCount[1]);
|
||||||
|
const value = count * def.harvestValue;
|
||||||
|
|
||||||
|
// Notify scene (UIScene bo prikazal)
|
||||||
|
this.scene.events.emit('harvest', {
|
||||||
|
item: def.harvestItem,
|
||||||
|
count,
|
||||||
|
value,
|
||||||
|
x: plot.x,
|
||||||
|
y: plot.y,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Floating nagrade tekst
|
||||||
|
this._showFloatingText(plot.x, plot.y - 20,
|
||||||
|
`🌾 ×${count} (+${value}g)`, '#ffd700', 28);
|
||||||
|
|
||||||
|
// Bounce animacija preden izgine
|
||||||
|
if (plot.sprite) {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: plot.sprite,
|
||||||
|
scaleX: plot.sprite.scaleX * 1.3,
|
||||||
|
scaleY: plot.sprite.scaleY * 1.3,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 400,
|
||||||
|
ease: 'Back.in',
|
||||||
|
onComplete: () => { if (plot.sprite) plot.sprite.destroy(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset plota (poorano ostane, crop zbrisano)
|
||||||
|
plot.crop = null;
|
||||||
|
plot.sprite = null;
|
||||||
|
plot.dayPlanted = null;
|
||||||
|
plot.dayWatered = null;
|
||||||
|
plot.stage = 0;
|
||||||
|
plot.wilted = false;
|
||||||
|
|
||||||
|
// Tla postanejo svetlejša (prazna)
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: plot.tileSprite,
|
||||||
|
alpha: 0.9,
|
||||||
|
duration: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🌾 Harvest: ${def.harvestItem} ×${count} = ${value}g`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// DNEVNI TICK — Kliči enkrat na dan (ob sleep/new day)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
onNewDay(day) {
|
||||||
|
this.currentDay = day;
|
||||||
|
|
||||||
|
for (const plot of this.tilledPlots) {
|
||||||
|
if (!plot.crop || plot.wilted) continue;
|
||||||
|
|
||||||
|
const def = this._getCropDef(plot.crop);
|
||||||
|
|
||||||
|
// ── Preveri zalivanje ──
|
||||||
|
if (plot.dayWatered < day - 1) {
|
||||||
|
// Ni bilo zalivano včeraj → Uvene!
|
||||||
|
this._wiltCrop(plot);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rast ──
|
||||||
|
const daysGrown = day - plot.dayPlanted;
|
||||||
|
// Enakomerno razporedimo stopnje čez daysToGrow
|
||||||
|
const newStage = Math.min(
|
||||||
|
Math.floor((daysGrown / def.daysToGrow) * def.stages),
|
||||||
|
def.stages - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newStage > plot.stage) {
|
||||||
|
plot.stage = newStage;
|
||||||
|
this._updateCropSprite(plot);
|
||||||
|
|
||||||
|
if (newStage === def.stages - 1) {
|
||||||
|
// ZRELA!
|
||||||
|
this._showFloatingText(plot.x, plot.y - 40, '🌟 Zrelo!', '#ffd700', 24);
|
||||||
|
this._glowPulse(plot.sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🌞 Dan ${day} tick — ${this.tilledPlots.filter(p => p.crop).length} crop-ov aktiv`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UVENJANJE
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_wiltCrop(plot) {
|
||||||
|
plot.wilted = true;
|
||||||
|
|
||||||
|
// Sprite potemni in se trese
|
||||||
|
if (plot.sprite) {
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: plot.sprite,
|
||||||
|
tint: 0x554433,
|
||||||
|
alpha: 0.5,
|
||||||
|
duration: 800,
|
||||||
|
ease: 'Sine.in',
|
||||||
|
onComplete: () => {
|
||||||
|
if (plot.sprite) plot.sprite.setTint(0x554433).setAlpha(0.45);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._showFloatingText(plot.x, plot.y - 30, '🍂 Uvelo!', '#cc6633');
|
||||||
|
console.warn(`🍂 Rastlina uvela @ ${plot.x},${plot.y}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// POMOŽNE METODE
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_getCropDef(key) {
|
||||||
|
return this.CROPS[key] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateCropSprite(plot) {
|
||||||
|
const def = this._getCropDef(plot.crop);
|
||||||
|
const stageKey = def.stageKeys[plot.stage];
|
||||||
|
|
||||||
|
// Ciljana višina: 20px (stage0) → 70px (zrela)
|
||||||
|
const minH = 20, maxH = 70;
|
||||||
|
const cropH = minH + (plot.stage / (def.stages - 1)) * (maxH - minH);
|
||||||
|
|
||||||
|
const texture = this.scene.textures.get(stageKey);
|
||||||
|
const srcH = texture?.getSourceImage()?.height || 150;
|
||||||
|
const scale = cropH / srcH;
|
||||||
|
|
||||||
|
if (plot.sprite) {
|
||||||
|
// Animiran prehod na novo stopnjo
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: plot.sprite,
|
||||||
|
scaleX: { from: plot.sprite.scaleX, to: scale },
|
||||||
|
scaleY: { from: plot.sprite.scaleY, to: scale },
|
||||||
|
duration: 600,
|
||||||
|
ease: 'Back.out',
|
||||||
|
onStart: () => { if (plot.sprite) plot.sprite.setTexture(stageKey); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeCrop(plot) {
|
||||||
|
if (plot.sprite) { plot.sprite.destroy(); plot.sprite = null; }
|
||||||
|
plot.crop = null;
|
||||||
|
plot.dayPlanted = null;
|
||||||
|
plot.dayWatered = null;
|
||||||
|
plot.stage = 0;
|
||||||
|
plot.wilted = false;
|
||||||
|
if (plot.tileSprite) plot.tileSprite.setAlpha(0.9).clearTint();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPlotNear(kai) {
|
||||||
|
let closest = null;
|
||||||
|
let minDist = this.interactRange;
|
||||||
|
for (const plot of this.tilledPlots) {
|
||||||
|
const d = Phaser.Math.Distance.Between(kai.x, kai.y, plot.x, plot.y);
|
||||||
|
if (d < minDist) { minDist = d; closest = plot; }
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getEmptyGroundNear(kai) {
|
||||||
|
// Snap na tile center — Kai mora biti v bližini centra tile-a
|
||||||
|
const ts = this.tileSize;
|
||||||
|
const snappedX = Math.floor(kai.x / ts) * ts + ts / 2;
|
||||||
|
const snappedY = Math.floor(kai.y / ts) * ts + ts / 2;
|
||||||
|
const dist = Phaser.Math.Distance.Between(kai.x, kai.y, snappedX, snappedY);
|
||||||
|
if (dist > this.interactRange) return null;
|
||||||
|
if (this.tilledPlots.some(p => Math.abs(p.x - snappedX) < 8 && Math.abs(p.y - snappedY) < 8)) return null;
|
||||||
|
return { x: snappedX, y: snappedY };
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawTooltip(plot) {
|
||||||
|
const def = plot.crop ? this._getCropDef(plot.crop) : null;
|
||||||
|
let label = '';
|
||||||
|
if (!plot.crop) label = '🌱 [E] Posadi';
|
||||||
|
else if (plot.wilted) label = '🍂 [E] Odstrani';
|
||||||
|
else if (plot.stage >= (def?.stages - 1)) label = '🌾 [E] Poženi!';
|
||||||
|
else if (this.activeTool === 'watering_can') label = '💧 [E] Zalij';
|
||||||
|
else label = `🌿 ${plot.crop} (${plot.stage + 1}/${def?.stages})`;
|
||||||
|
|
||||||
|
// Tooltip — svetla, jasna, pri vrhu tilled tile-a
|
||||||
|
if (!this._tooltipText) {
|
||||||
|
this._tooltipText = this.scene.add.text(0, 0, '', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#ffffff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
backgroundColor: '#00000099',
|
||||||
|
padding: { x: 8, y: 4 },
|
||||||
|
}).setDepth(9999).setScrollFactor(1);
|
||||||
|
}
|
||||||
|
const W = 48, H = 24;
|
||||||
|
this._tooltipText
|
||||||
|
.setText(label)
|
||||||
|
.setPosition(plot.x - this._tooltipText.width / 2, plot.y - H / 2 - 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showFloatingText(x, y, msg, color = '#ffffff', fontSize = 20) {
|
||||||
|
const txt = this.scene.add.text(x, y, msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: `${fontSize}px`,
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5).setDepth(9998);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
y: y - 60,
|
||||||
|
alpha: { from: 1, to: 0 },
|
||||||
|
duration: 1400,
|
||||||
|
ease: 'Cubic.out',
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_waterRipple(x, y) {
|
||||||
|
const gfx = this.scene.add.graphics().setDepth(9997);
|
||||||
|
let r = 4, alpha = 0.7;
|
||||||
|
const expand = this.scene.time.addEvent({
|
||||||
|
delay: 40,
|
||||||
|
repeat: 8,
|
||||||
|
callback: () => {
|
||||||
|
gfx.clear();
|
||||||
|
gfx.lineStyle(2, 0x4499ff, alpha);
|
||||||
|
gfx.strokeCircle(x, y, r);
|
||||||
|
r += 6; alpha -= 0.08;
|
||||||
|
if (r > 55) { gfx.destroy(); expand.remove(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_glowPulse(sprite) {
|
||||||
|
if (!sprite) return;
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: sprite,
|
||||||
|
alpha: { from: 1, to: 0.5 },
|
||||||
|
duration: 400,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: 3,
|
||||||
|
ease: 'Sine.inOut',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PUBLIC API — Za GrassScene
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Nastavi aktiven tool (kliče InventorySystem) */
|
||||||
|
setTool(tool) { this.activeTool = tool; }
|
||||||
|
|
||||||
|
/** Nastavi aktivno seme (kliče InventorySystem) */
|
||||||
|
setSeed(seed) { this.activeSeed = seed; }
|
||||||
|
|
||||||
|
/** Vrni vse activate plots (za save) */
|
||||||
|
getState() {
|
||||||
|
return this.tilledPlots.map(p => ({
|
||||||
|
x: p.x, y: p.y,
|
||||||
|
crop: p.crop,
|
||||||
|
dayPlanted: p.dayPlanted,
|
||||||
|
dayWatered: p.dayWatered,
|
||||||
|
stage: p.stage,
|
||||||
|
wilted: p.wilted,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destroy */
|
||||||
|
destroy() {
|
||||||
|
this.highlightGfx?.destroy();
|
||||||
|
this._tooltipText?.destroy();
|
||||||
|
this.tilledPlots.forEach(p => {
|
||||||
|
p.tileSprite?.destroy();
|
||||||
|
p.sprite?.destroy();
|
||||||
|
});
|
||||||
|
this.tilledPlots = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
326
nova farma TRAE/src/systems/InventorySystem.js
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* InventorySystem.js — Nova Farma
|
||||||
|
* ============================================================
|
||||||
|
* Hotbar (7 slotov) + Items logika
|
||||||
|
*
|
||||||
|
* DEMO starter items:
|
||||||
|
* Slot 1: 🪓 Motika (Hoe) → ore tla
|
||||||
|
* Slot 2: 💧 Zalivalka → zalij crop
|
||||||
|
* Slot 3: 🌾 Pšenica semena ×5
|
||||||
|
* Slot 4: 🥕 Korenje semena ×5
|
||||||
|
* Slot 5: — prazen —
|
||||||
|
* Slot 6: — prazen —
|
||||||
|
* Slot 7: — prazen —
|
||||||
|
*
|
||||||
|
* Keys: [1]-[7] za slot, scroll za rotate
|
||||||
|
* Emit 'hotbarChanged' { slot, item } → GrassScene → FarmingSystem
|
||||||
|
* ============================================================
|
||||||
|
*/
|
||||||
|
export default class InventorySystem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Phaser.Scene} scene — GrassSceneClean
|
||||||
|
*/
|
||||||
|
constructor(scene) {
|
||||||
|
this.scene = scene;
|
||||||
|
|
||||||
|
// Vsi predmeti (full inventory)
|
||||||
|
this.items = {};
|
||||||
|
// Format: { 'wheat_seed': { name, icon, count, type, tool/seed } }
|
||||||
|
|
||||||
|
// Hotbar = 7 slotov, vsak kaže na item key ali null
|
||||||
|
this.hotbar = [
|
||||||
|
'hoe',
|
||||||
|
'watering_can',
|
||||||
|
'wheat_seed',
|
||||||
|
'carrot_seed',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
this.activeSlot = 0; // Slot 1 = Motika
|
||||||
|
|
||||||
|
// Item definicije
|
||||||
|
this.ITEM_DEFS = {
|
||||||
|
hoe: {
|
||||||
|
name: 'Motika',
|
||||||
|
icon: '🪓',
|
||||||
|
type: 'tool',
|
||||||
|
tool: 'hoe',
|
||||||
|
stackable: false,
|
||||||
|
count: 1,
|
||||||
|
desc: '[E] Oraj tla',
|
||||||
|
},
|
||||||
|
watering_can: {
|
||||||
|
name: 'Zalivalka',
|
||||||
|
icon: '💧',
|
||||||
|
type: 'tool',
|
||||||
|
tool: 'watering_can',
|
||||||
|
stackable: false,
|
||||||
|
count: 1,
|
||||||
|
desc: '[E] Zalij rastlino',
|
||||||
|
},
|
||||||
|
wheat_seed: {
|
||||||
|
name: 'Pšenica',
|
||||||
|
icon: '🌾',
|
||||||
|
type: 'seed',
|
||||||
|
seed: 'wheat',
|
||||||
|
stackable: true,
|
||||||
|
count: 5,
|
||||||
|
desc: '3 dni • 30g/harvest',
|
||||||
|
color: '#ffdd88',
|
||||||
|
},
|
||||||
|
carrot_seed: {
|
||||||
|
name: 'Korenje',
|
||||||
|
icon: '🥕',
|
||||||
|
type: 'seed',
|
||||||
|
seed: 'carrot',
|
||||||
|
stackable: true,
|
||||||
|
count: 5,
|
||||||
|
desc: '4 dni • 40g/harvest',
|
||||||
|
color: '#ff8844',
|
||||||
|
},
|
||||||
|
cannabis_seed: {
|
||||||
|
name: 'Konoplja',
|
||||||
|
icon: '🌿',
|
||||||
|
type: 'seed',
|
||||||
|
seed: 'cannabis',
|
||||||
|
stackable: true,
|
||||||
|
count: 0,
|
||||||
|
desc: '7 dni • 200g/harvest',
|
||||||
|
color: '#88cc44',
|
||||||
|
},
|
||||||
|
// Harvested items
|
||||||
|
wheat: {
|
||||||
|
name: 'Pšenica',
|
||||||
|
icon: '🌾',
|
||||||
|
type: 'resource',
|
||||||
|
stackable: true,
|
||||||
|
count: 0,
|
||||||
|
value: 30,
|
||||||
|
},
|
||||||
|
carrot: {
|
||||||
|
name: 'Korenje',
|
||||||
|
icon: '🥕',
|
||||||
|
type: 'resource',
|
||||||
|
stackable: true,
|
||||||
|
count: 0,
|
||||||
|
value: 40,
|
||||||
|
},
|
||||||
|
cannabis_bud: {
|
||||||
|
name: 'Konoplja',
|
||||||
|
icon: '🌿',
|
||||||
|
type: 'resource',
|
||||||
|
stackable: true,
|
||||||
|
count: 0,
|
||||||
|
value: 200,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inicializiraj items iz ITEM_DEFS (samo tiste s count > 0)
|
||||||
|
for (const [key, def] of Object.entries(this.ITEM_DEFS)) {
|
||||||
|
this.items[key] = { ...def };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Denar (Gold)
|
||||||
|
this.gold = 0;
|
||||||
|
|
||||||
|
// Number keys [1]-[7]
|
||||||
|
for (let i = 1; i <= 7; i++) {
|
||||||
|
scene.input.keyboard.on(`keydown-${i}`, () => this.selectSlot(i - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll wheel za rotation
|
||||||
|
scene.input.on('wheel', (pointer, gObj, dx, dy) => {
|
||||||
|
if (dy > 0) this.selectSlot((this.activeSlot + 1) % 7);
|
||||||
|
else if (dy < 0) this.selectSlot((this.activeSlot + 6) % 7);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Poslušaj harvest evente → dodaj v inventar
|
||||||
|
scene.events.on('harvest', (data) => {
|
||||||
|
this.addItem(data.item, data.count);
|
||||||
|
this.addGold(data.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prvič sinhronizira z FarmingSystem
|
||||||
|
this._syncWithFarming();
|
||||||
|
|
||||||
|
console.log('🎒 InventorySystem initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// IZBERI SLOT
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
selectSlot(index) {
|
||||||
|
if (index < 0 || index >= 7) return;
|
||||||
|
this.activeSlot = index;
|
||||||
|
this._syncWithFarming();
|
||||||
|
this._notifyUI();
|
||||||
|
console.log(`🎒 Slot ${index + 1}: ${this.hotbar[index] || 'prazen'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// DODAJ ITEM
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
addItem(key, count = 1) {
|
||||||
|
if (!this.items[key]) {
|
||||||
|
this.items[key] = { ...(this.ITEM_DEFS[key] || { name: key, icon: '📦', count: 0 }) };
|
||||||
|
}
|
||||||
|
this.items[key].count += count;
|
||||||
|
|
||||||
|
// Dodaj v hotbar če je prazen slot + seme/resource
|
||||||
|
const def = this.ITEM_DEFS[key];
|
||||||
|
if (def && def.stackable && !this.hotbar.includes(key)) {
|
||||||
|
const emptySlot = this.hotbar.findIndex(s => s === null);
|
||||||
|
if (emptySlot !== -1) this.hotbar[emptySlot] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notifyUI();
|
||||||
|
console.log(`🎒 +${count}× ${key} (skupaj: ${this.items[key].count})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGold(amount) {
|
||||||
|
this.gold += amount;
|
||||||
|
this._notifyUI();
|
||||||
|
console.log(`💰 +${amount}g (skupaj: ${this.gold}g)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PORABI ITEM (semena pri sajenju)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
useActiveItem() {
|
||||||
|
const key = this.hotbar[this.activeSlot];
|
||||||
|
if (!key) return false;
|
||||||
|
const item = this.items[key];
|
||||||
|
if (!item) return false;
|
||||||
|
|
||||||
|
if (item.type === 'seed') {
|
||||||
|
if (item.count <= 0) {
|
||||||
|
this._showMsg(`❌ Ni ${item.name} semen!`, '#ff6666');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
item.count--;
|
||||||
|
if (item.count <= 0 && item.stackable) {
|
||||||
|
// Odstrani iz hotbara
|
||||||
|
this.hotbar[this.activeSlot] = null;
|
||||||
|
}
|
||||||
|
this._notifyUI();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === 'tool') return true; // Orodja se ne porabijo
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// SYNC z FarmingSystem
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_syncWithFarming() {
|
||||||
|
const farming = this.scene.farmingSystem;
|
||||||
|
if (!farming) return;
|
||||||
|
|
||||||
|
const key = this.hotbar[this.activeSlot];
|
||||||
|
if (!key) {
|
||||||
|
farming.setTool(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = this.items[key];
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
if (item.type === 'tool') {
|
||||||
|
farming.setTool(item.tool);
|
||||||
|
farming.setSeed(null);
|
||||||
|
} else if (item.type === 'seed') {
|
||||||
|
farming.setTool('hoe'); // Semena → avtomatsko orodje = motika za saditev
|
||||||
|
farming.setSeed(item.seed);
|
||||||
|
} else {
|
||||||
|
farming.setTool(null);
|
||||||
|
farming.setSeed(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// NOTIFY UIScene
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_notifyUI() {
|
||||||
|
const uiScene = this.scene.scene.get('UIScene');
|
||||||
|
if (!uiScene) return;
|
||||||
|
|
||||||
|
// Pošlji hotbar stanje
|
||||||
|
const hotbarData = this.hotbar.map((key, i) => {
|
||||||
|
if (!key) return null;
|
||||||
|
const item = this.items[key];
|
||||||
|
const def = this.ITEM_DEFS[key];
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
icon: def?.icon || '📦',
|
||||||
|
name: def?.name || key,
|
||||||
|
count: item?.count ?? 1,
|
||||||
|
active: i === this.activeSlot,
|
||||||
|
type: def?.type || 'unknown',
|
||||||
|
stackable: def?.stackable || false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
uiScene.events.emit('hotbarUpdate', {
|
||||||
|
slots: hotbarData,
|
||||||
|
activeSlot: this.activeSlot,
|
||||||
|
gold: this.gold,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_showMsg(msg, color = '#ffffff') {
|
||||||
|
const cam = this.scene.cameras.main;
|
||||||
|
const txt = this.scene.add.text(
|
||||||
|
cam.worldView.centerX,
|
||||||
|
cam.worldView.top + 80,
|
||||||
|
msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '20px',
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}
|
||||||
|
).setOrigin(0.5).setDepth(9999);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
y: txt.y - 40,
|
||||||
|
alpha: { from: 1, to: 0 },
|
||||||
|
duration: 1500,
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PUBLIC API
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
getActiveItem() {
|
||||||
|
const key = this.hotbar[this.activeSlot];
|
||||||
|
return key ? this.items[key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveItemKey() {
|
||||||
|
return this.hotbar[this.activeSlot];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCount(key) {
|
||||||
|
return this.items[key]?.count || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getGold() {
|
||||||
|
return this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return {
|
||||||
|
items: this.items,
|
||||||
|
hotbar: this.hotbar,
|
||||||
|
activeSlot: this.activeSlot,
|
||||||
|
gold: this.gold,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
329
nova farma TRAE/src/systems/WaterSystem.js
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* WaterSystem.js — Nova Farma (DEMO)
|
||||||
|
* ============================================================
|
||||||
|
* Voda je omejena v Demu — edini vir = Rain Catcher.
|
||||||
|
*
|
||||||
|
* Logika:
|
||||||
|
* - Rain Catcher zbira vodo med dežjem (triggerRainfall)
|
||||||
|
* - Max kapaciteta = 20 L na Rain Catcher
|
||||||
|
* - Zalivanje porabi 1 L na crop
|
||||||
|
* - Kai dobi "vedro" (watering_can) s 5L kapaciteto
|
||||||
|
* - [E] blizu Rain Catcher → Napolni vedro iz Rain Catcher-ja
|
||||||
|
* - UIScene prikaže: 💧 vedro: 3/5 | 🪣 RC: 12/20
|
||||||
|
* ============================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class WaterSystem {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Phaser.Scene} scene
|
||||||
|
* @param {object} options — { islandX, islandY, tileSize }
|
||||||
|
*/
|
||||||
|
constructor(scene, options = {}) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.tileSize = options.tileSize || 128;
|
||||||
|
|
||||||
|
// === Vedro (Watering Can) ===
|
||||||
|
this.canCurrent = 3; // Začne s 3L
|
||||||
|
this.canMax = 5; // Max 5L v vedru
|
||||||
|
this.canPerCrop = 1; // 1L na zalivanje
|
||||||
|
|
||||||
|
// === Rain Catcher objekti ===
|
||||||
|
this.rainCatchers = []; // { x, y, sprite, current, max, label }
|
||||||
|
|
||||||
|
// === Highlights ===
|
||||||
|
this.highlightGfx = scene.add.graphics().setDepth(9001);
|
||||||
|
|
||||||
|
// === Input [F] za napolnit vedro ===
|
||||||
|
this.keyF = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F);
|
||||||
|
|
||||||
|
// === Interact razdalja ===
|
||||||
|
this.interactRange = 100;
|
||||||
|
|
||||||
|
// === Dežjni multiplier ===
|
||||||
|
this.rainCollectRate = 4; // L na dežen dan na RC
|
||||||
|
|
||||||
|
console.log('💧 WaterSystem initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PRELOAD
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
static preload(scene) {
|
||||||
|
scene.load.image('rain_catcher', 'DEMO_FAZA1/Structures/rain_catcher.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PLACE RAIN CATCHER (kliče se ob postavitvi zgradbe ali ob init)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
placeRainCatcher(x, y) {
|
||||||
|
const targetH = 100; // px višina v svetu
|
||||||
|
const texture = this.scene.textures.get('rain_catcher');
|
||||||
|
const srcH = texture?.getSourceImage()?.height || 512;
|
||||||
|
const scale = targetH / srcH;
|
||||||
|
|
||||||
|
const sprite = this.scene.add.image(x, y, 'rain_catcher')
|
||||||
|
.setScale(scale)
|
||||||
|
.setOrigin(0.5, 1.0)
|
||||||
|
.setDepth(y)
|
||||||
|
.setInteractive();
|
||||||
|
|
||||||
|
// Label nad RC
|
||||||
|
const label = this.scene.add.text(x, y - targetH - 8, '🪣 0/20L', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#44aaff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 3,
|
||||||
|
}).setOrigin(0.5, 1.0).setDepth(y + 1);
|
||||||
|
|
||||||
|
const rc = {
|
||||||
|
x, y,
|
||||||
|
sprite,
|
||||||
|
label,
|
||||||
|
current: 0,
|
||||||
|
max: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.rainCatchers.push(rc);
|
||||||
|
this._updateLabel(rc);
|
||||||
|
|
||||||
|
// Pop-in animacija
|
||||||
|
sprite.setAlpha(0).setScale(scale * 0.1);
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: sprite,
|
||||||
|
alpha: 1,
|
||||||
|
scaleX: scale,
|
||||||
|
scaleY: scale,
|
||||||
|
duration: 500,
|
||||||
|
ease: 'Back.out',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🪣 Rain Catcher postavljen @ ${x}, ${y}`);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UPDATE — kliči iz scene.update()
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
update(kai) {
|
||||||
|
this.highlightGfx.clear();
|
||||||
|
|
||||||
|
const nearRC = this._getRCNear(kai);
|
||||||
|
|
||||||
|
if (nearRC) {
|
||||||
|
// Highlight RC
|
||||||
|
this.highlightGfx.lineStyle(2, 0x44aaff, 0.9);
|
||||||
|
this.highlightGfx.strokeCircle(nearRC.x, nearRC.y - 50, 60);
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
this._showRCTooltip(nearRC);
|
||||||
|
} else {
|
||||||
|
this._hideRCTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// [F] — Napolni vedro
|
||||||
|
if (Phaser.Input.Keyboard.JustDown(this.keyF)) {
|
||||||
|
if (nearRC) {
|
||||||
|
this._fillCan(nearRC);
|
||||||
|
} else {
|
||||||
|
this._showFloatingTextAt(kai.x, kai.y - 40, '🪣 Bliže Rain Catcher!', '#aaaaff', 14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// NAPOLNI VEDRO iz Rain Catcher-ja
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_fillCan(rc) {
|
||||||
|
if (rc.current <= 0) {
|
||||||
|
this._showFloatingTextAt(rc.x, rc.y - 110, '❌ RC je prazen!', '#ff6666');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.canCurrent >= this.canMax) {
|
||||||
|
this._showFloatingTextAt(rc.x, rc.y - 110, '✅ Vedro je polno!', '#44ffcc');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const needed = this.canMax - this.canCurrent;
|
||||||
|
const take = Math.min(needed, rc.current);
|
||||||
|
|
||||||
|
rc.current -= take;
|
||||||
|
this.canCurrent += take;
|
||||||
|
|
||||||
|
this._updateLabel(rc);
|
||||||
|
this.scene.events.emit('waterChanged', {
|
||||||
|
can: this.canCurrent,
|
||||||
|
canMax: this.canMax,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animacija — vodni curek (modri krogci padajo v vedro)
|
||||||
|
this._fillAnimation(rc.x, rc.y - 30);
|
||||||
|
this._showFloatingTextAt(rc.x, rc.y - 110, `💧 +${take}L (vedro: ${this.canCurrent}/${this.canMax}L)`, '#44aaff', 16);
|
||||||
|
console.log(`💧 Napolnjeno: +${take}L. Vedro: ${this.canCurrent}/${this.canMax}L. RC: ${rc.current}/${rc.max}L`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PORABI VODO ZA ZALIVANJE (kliče FarmingSystem)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
useWater(amount = 1) {
|
||||||
|
if (this.canCurrent < amount) return false; // Ni dovolj vode!
|
||||||
|
this.canCurrent -= amount;
|
||||||
|
this.scene.events.emit('waterChanged', {
|
||||||
|
can: this.canCurrent,
|
||||||
|
canMax: this.canMax,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasWater(amount = 1) {
|
||||||
|
return this.canCurrent >= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// DEŽNI TICK — kliči ob dežju (triggerRainfall v GrassScene)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
onRain() {
|
||||||
|
let total = 0;
|
||||||
|
for (const rc of this.rainCatchers) {
|
||||||
|
const collected = Math.min(this.rainCollectRate, rc.max - rc.current);
|
||||||
|
rc.current += collected;
|
||||||
|
total += collected;
|
||||||
|
this._updateLabel(rc);
|
||||||
|
// Ripple animacija na RC
|
||||||
|
this._rainRipple(rc.x, rc.y - 40);
|
||||||
|
}
|
||||||
|
if (total > 0) {
|
||||||
|
console.log(`🌧️ Dež zbral ${total}L v Rain Catcher-jih`);
|
||||||
|
}
|
||||||
|
this.scene.events.emit('waterChanged', {
|
||||||
|
can: this.canCurrent,
|
||||||
|
canMax: this.canMax,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// POMOŽNE METODE
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_getRCNear(kai) {
|
||||||
|
let closest = null;
|
||||||
|
let minDist = this.interactRange;
|
||||||
|
for (const rc of this.rainCatchers) {
|
||||||
|
const d = Phaser.Math.Distance.Between(kai.x, kai.y, rc.x, rc.y);
|
||||||
|
if (d < minDist) { minDist = d; closest = rc; }
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateLabel(rc) {
|
||||||
|
const pct = rc.current / rc.max;
|
||||||
|
const color = pct > 0.5 ? '#44aaff' : pct > 0.2 ? '#ffaa00' : '#ff4444';
|
||||||
|
rc.label
|
||||||
|
.setText(`🪣 ${rc.current}/${rc.max}L`)
|
||||||
|
.setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showRCTooltip(rc) {
|
||||||
|
if (!this._rcTooltip) {
|
||||||
|
this._rcTooltip = this.scene.add.text(0, 0, '', {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '13px',
|
||||||
|
color: '#ffffff',
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
backgroundColor: '#00000099',
|
||||||
|
padding: { x: 8, y: 4 },
|
||||||
|
}).setDepth(9999).setScrollFactor(1);
|
||||||
|
}
|
||||||
|
const label = this.canCurrent >= this.canMax
|
||||||
|
? `✅ Vedro polno (${this.canCurrent}L)`
|
||||||
|
: `[F] Napolni vedro 💧${this.canCurrent}/${this.canMax}L`;
|
||||||
|
this._rcTooltip
|
||||||
|
.setText(label)
|
||||||
|
.setAlpha(1)
|
||||||
|
.setPosition(rc.x - this._rcTooltip.width / 2, rc.y - 115);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideRCTooltip() {
|
||||||
|
if (this._rcTooltip) this._rcTooltip.setAlpha(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fillAnimation(x, y) {
|
||||||
|
// Padajoče kapljice
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const drop = this.scene.add.graphics().setDepth(9998);
|
||||||
|
const ox = x + Phaser.Math.Between(-15, 15);
|
||||||
|
drop.fillStyle(0x44aaff, 0.8);
|
||||||
|
drop.fillEllipse(ox, y, 4, 8);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: drop,
|
||||||
|
y: y + 30 + Phaser.Math.Between(0, 20),
|
||||||
|
alpha: 0,
|
||||||
|
delay: i * 60,
|
||||||
|
duration: 300,
|
||||||
|
ease: 'Quad.in',
|
||||||
|
onComplete: () => drop.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_rainRipple(x, y) {
|
||||||
|
const gfx = this.scene.add.graphics().setDepth(9997);
|
||||||
|
let r = 2, alpha = 0.6;
|
||||||
|
const ev = this.scene.time.addEvent({
|
||||||
|
delay: 50,
|
||||||
|
repeat: 5,
|
||||||
|
callback: () => {
|
||||||
|
gfx.clear();
|
||||||
|
gfx.lineStyle(1, 0x44aaff, alpha);
|
||||||
|
gfx.strokeEllipse(x, y, r * 2, r);
|
||||||
|
r += 4; alpha -= 0.1;
|
||||||
|
if (r > 25) { gfx.destroy(); ev.remove(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_showFloatingTextAt(x, y, msg, color = '#ffffff', fontSize = 18) {
|
||||||
|
const txt = this.scene.add.text(x, y, msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: `${fontSize}px`,
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5).setDepth(9998);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
y: y - 50,
|
||||||
|
alpha: { from: 1, to: 0 },
|
||||||
|
duration: 1600,
|
||||||
|
ease: 'Cubic.out',
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PUBLIC API
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Vrni stanje za save */
|
||||||
|
getState() {
|
||||||
|
return {
|
||||||
|
can: this.canCurrent,
|
||||||
|
catchers: this.rainCatchers.map(rc => ({ x: rc.x, y: rc.y, current: rc.current })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.highlightGfx?.destroy();
|
||||||
|
this._rcTooltip?.destroy();
|
||||||
|
this.rainCatchers.forEach(rc => {
|
||||||
|
rc.sprite?.destroy();
|
||||||
|
rc.label?.destroy();
|
||||||
|
});
|
||||||
|
this.rainCatchers = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
466
nova farma TRAE/src/systems/ZombieSystem.js
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
/**
|
||||||
|
* ============================================================
|
||||||
|
* ZombieSystem.js — Nova Farma Demo
|
||||||
|
* ============================================================
|
||||||
|
* Shambler zombi AI:
|
||||||
|
* - Spawn ob robu otoka (ponoči)
|
||||||
|
* - Počasi sledi Kai-u (shambling walk)
|
||||||
|
* - [SPACE] blizu → Alfa Moč → zombi udomačen
|
||||||
|
* - Udomačen zombi sledi Kai-u kot spremljevalec
|
||||||
|
* - Max 3 aktivni zombiji na otoku
|
||||||
|
*
|
||||||
|
* Sprite sheet: zombie_shambler_walk_sheet.png
|
||||||
|
* Format: 4 stolpci × 4 vrstice = 16 frameov
|
||||||
|
* Frame size: 160×160px (640px sheet / 4)
|
||||||
|
* Vrstice: 0=DOWN, 1=LEFT, 2=RIGHT, 3=UP
|
||||||
|
* ============================================================
|
||||||
|
*/
|
||||||
|
export default class ZombieSystem {
|
||||||
|
|
||||||
|
constructor(scene, options = {}) {
|
||||||
|
this.scene = scene;
|
||||||
|
|
||||||
|
// Otok meje (za spawn na robu)
|
||||||
|
this.islandX = options.islandX || 2000;
|
||||||
|
this.islandY = options.islandY || 2000;
|
||||||
|
this.islandW = options.islandW || 6400;
|
||||||
|
this.islandH = options.islandH || 6400;
|
||||||
|
|
||||||
|
// Zombi parametri
|
||||||
|
this.maxZombies = 3;
|
||||||
|
this.spawnAtNight = true;
|
||||||
|
this.zombieSpeed = 35; // px/s — počasi šambers
|
||||||
|
this.detectionRange = 600; // Kai zazna zombija → prikaže opozorilo
|
||||||
|
this.alfaRange = 120; // Razdalja za Alfa Moč [SPACE]
|
||||||
|
this.tamingTime = 2000; // ms za udomačitev (drži [SPACE])
|
||||||
|
|
||||||
|
// Aktivni zombiji
|
||||||
|
this.zombies = [];
|
||||||
|
|
||||||
|
// Alfa moč (SPACE)
|
||||||
|
this.keySpace = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
|
||||||
|
this._alfaProgress = 0;
|
||||||
|
this._alfaGfx = scene.add.graphics().setDepth(9005);
|
||||||
|
this._alfaBar = null;
|
||||||
|
|
||||||
|
// Spawn timer
|
||||||
|
this.spawnTimer = null;
|
||||||
|
|
||||||
|
// Phaser groups
|
||||||
|
this.zombieGroup = scene.add.group();
|
||||||
|
|
||||||
|
console.log('🧟 ZombieSystem initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PRELOAD
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
static preload(scene) {
|
||||||
|
// Frame size: 640/4 = 160px
|
||||||
|
scene.load.spritesheet('zombie_shambler', 'DEMO_FAZA1/Characters/zombie_shambler_walk_sheet.png', {
|
||||||
|
frameWidth: 160,
|
||||||
|
frameHeight: 160,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// CREATE ANIMATIONS
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
createAnimations() {
|
||||||
|
const fps = 6;
|
||||||
|
const anims = [
|
||||||
|
{ key: 'zombie_walk_down', frames: [0, 1, 2, 3] },
|
||||||
|
{ key: 'zombie_walk_left', frames: [4, 5, 6, 7] },
|
||||||
|
{ key: 'zombie_walk_right', frames: [8, 9, 10, 11] },
|
||||||
|
{ key: 'zombie_walk_up', frames: [12, 13, 14, 15] },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const anim of anims) {
|
||||||
|
if (this.scene.anims.exists(anim.key)) continue;
|
||||||
|
this.scene.anims.create({
|
||||||
|
key: anim.key,
|
||||||
|
frames: this.scene.anims.generateFrameNumbers('zombie_shambler', {
|
||||||
|
frames: anim.frames,
|
||||||
|
}),
|
||||||
|
frameRate: fps,
|
||||||
|
repeat: -1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('🧟 Zombie animations created');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// SPAWN TIMER — Zaženi ob noč eventa
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
startNightSpawning() {
|
||||||
|
if (this.spawnTimer) return;
|
||||||
|
this.spawnTimer = this.scene.time.addEvent({
|
||||||
|
delay: 12000, // Vsakih 12s poskusi spawnat (ponoči)
|
||||||
|
loop: true,
|
||||||
|
callback: () => {
|
||||||
|
if (this.zombies.length < this.maxZombies) {
|
||||||
|
this.spawnZombie();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Takoj spawni prvega
|
||||||
|
this.scene.time.delayedCall(2000, () => {
|
||||||
|
if (this.zombies.length < this.maxZombies) this.spawnZombie();
|
||||||
|
});
|
||||||
|
console.log('🌙 Zombiji se začnejo spawnat!');
|
||||||
|
}
|
||||||
|
|
||||||
|
stopNightSpawning() {
|
||||||
|
if (this.spawnTimer) {
|
||||||
|
this.spawnTimer.remove();
|
||||||
|
this.spawnTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// SPAWN POSAMIČNEGA ZOMBIJA
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
spawnZombie(x = null, y = null) {
|
||||||
|
// Spawn na naključnem robu otoka
|
||||||
|
if (x === null || y === null) {
|
||||||
|
const edge = Phaser.Math.Between(0, 3);
|
||||||
|
const margin = 200;
|
||||||
|
switch (edge) {
|
||||||
|
case 0: // Zgornji rob
|
||||||
|
x = this.islandX + Phaser.Math.Between(margin, this.islandW - margin);
|
||||||
|
y = this.islandY + margin;
|
||||||
|
break;
|
||||||
|
case 1: // Desni rob
|
||||||
|
x = this.islandX + this.islandW - margin;
|
||||||
|
y = this.islandY + Phaser.Math.Between(margin, this.islandH - margin);
|
||||||
|
break;
|
||||||
|
case 2: // Spodnji rob
|
||||||
|
x = this.islandX + Phaser.Math.Between(margin, this.islandW - margin);
|
||||||
|
y = this.islandY + this.islandH - margin;
|
||||||
|
break;
|
||||||
|
case 3: // Levi rob
|
||||||
|
x = this.islandX + margin;
|
||||||
|
y = this.islandY + Phaser.Math.Between(margin, this.islandH - margin);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprite = this.scene.add.sprite(x, y, 'zombie_shambler', 0)
|
||||||
|
.setScale(0.55) // 160px × 0.55 ≈ 88px — primerljivo s Kai-jem
|
||||||
|
.setOrigin(0.5, 1.0)
|
||||||
|
.setDepth(y);
|
||||||
|
|
||||||
|
// Pop-in z mgle
|
||||||
|
sprite.setAlpha(0);
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: sprite,
|
||||||
|
alpha: 1,
|
||||||
|
duration: 800,
|
||||||
|
ease: 'Sine.in',
|
||||||
|
});
|
||||||
|
|
||||||
|
sprite.play('zombie_walk_down');
|
||||||
|
|
||||||
|
// Rdeč halo efekt
|
||||||
|
const halo = this.scene.add.graphics().setDepth(y - 1);
|
||||||
|
halo.fillStyle(0xff0000, 0.15);
|
||||||
|
halo.fillCircle(x, y - 20, 35);
|
||||||
|
|
||||||
|
const zombie = {
|
||||||
|
sprite,
|
||||||
|
halo,
|
||||||
|
x, y,
|
||||||
|
state: 'wandering', // 'wandering' | 'chasing' | 'tamed'
|
||||||
|
tamed: false,
|
||||||
|
hp: 30,
|
||||||
|
maxHp: 30,
|
||||||
|
dir: 'down',
|
||||||
|
wanderTimer: 0,
|
||||||
|
wanderDirX: 0,
|
||||||
|
wanderDirY: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.zombies.push(zombie);
|
||||||
|
console.log(`🧟 Zombi spawnan @ ${Math.round(x)},${Math.round(y)}. Skupaj: ${this.zombies.length}`);
|
||||||
|
return zombie;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// UPDATE — Kliči vsak frame iz scene.update()
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
update(kai, delta) {
|
||||||
|
this._alfaGfx.clear();
|
||||||
|
|
||||||
|
const nearestHostile = this._getNearestHostile(kai);
|
||||||
|
|
||||||
|
// === ALFA MOČ [SPACE] ===
|
||||||
|
if (nearestHostile && Phaser.Math.Distance.Between(
|
||||||
|
kai.x, kai.y, nearestHostile.sprite.x, nearestHostile.sprite.y
|
||||||
|
) < this.alfaRange) {
|
||||||
|
|
||||||
|
if (this.keySpace.isDown) {
|
||||||
|
this._alfaProgress += delta;
|
||||||
|
this._drawAlfaBar(kai, nearestHostile);
|
||||||
|
|
||||||
|
if (this._alfaProgress >= this.tamingTime) {
|
||||||
|
this._tameZombie(nearestHostile, kai);
|
||||||
|
this._alfaProgress = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._alfaProgress = Math.max(0, this.alfaProgress - delta * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hint tekst
|
||||||
|
if (!this._spaceHint || !this._spaceHint.active) {
|
||||||
|
this._spaceHint = this._showHint(
|
||||||
|
nearestHostile.sprite.x,
|
||||||
|
nearestHostile.sprite.y - 80,
|
||||||
|
'[SPACE] Alfa Moč!', '#ff4444'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._alfaProgress = 0;
|
||||||
|
if (this._spaceHint) { this._spaceHint.destroy(); this._spaceHint = null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// === POSODOBI VSE ZOMBIJE ===
|
||||||
|
for (let i = this.zombies.length - 1; i >= 0; i--) {
|
||||||
|
const z = this.zombies[i];
|
||||||
|
if (!z.sprite || !z.sprite.active) {
|
||||||
|
this.zombies.splice(i, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateZombie(z, kai, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// POSODOBI POSAMIČNEGA ZOMBIJA
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_updateZombie(z, kai, delta) {
|
||||||
|
const dt = delta / 1000;
|
||||||
|
const speed = z.tamed ? this.zombieSpeed * 1.3 : this.zombieSpeed;
|
||||||
|
|
||||||
|
const distToKai = Phaser.Math.Distance.Between(
|
||||||
|
z.sprite.x, z.sprite.y, kai.x, kai.y
|
||||||
|
);
|
||||||
|
|
||||||
|
let moveX = 0, moveY = 0;
|
||||||
|
|
||||||
|
if (z.tamed) {
|
||||||
|
// === UDOMAČEN: sledi Kai-u na razdalji 80px ===
|
||||||
|
if (distToKai > 80) {
|
||||||
|
const angle = Phaser.Math.Angle.Between(z.sprite.x, z.sprite.y, kai.x, kai.y);
|
||||||
|
moveX = Math.cos(angle) * speed * dt;
|
||||||
|
moveY = Math.sin(angle) * speed * dt;
|
||||||
|
}
|
||||||
|
// Zeleni halo za udomačene
|
||||||
|
z.halo.clear();
|
||||||
|
z.halo.fillStyle(0x44ff88, 0.2);
|
||||||
|
z.halo.fillCircle(z.sprite.x, z.sprite.y - 20, 35);
|
||||||
|
|
||||||
|
} else if (distToKai < 450) {
|
||||||
|
// === ZAZNALI KAI-A: Lovijo ===
|
||||||
|
z.state = 'chasing';
|
||||||
|
const angle = Phaser.Math.Angle.Between(z.sprite.x, z.sprite.y, kai.x, kai.y);
|
||||||
|
moveX = Math.cos(angle) * speed * dt;
|
||||||
|
moveY = Math.sin(angle) * speed * dt;
|
||||||
|
|
||||||
|
// Rdeči halo (nevarnost)
|
||||||
|
z.halo.clear();
|
||||||
|
z.halo.fillStyle(0xff0000, 0.18);
|
||||||
|
z.halo.fillCircle(z.sprite.x, z.sprite.y - 20, 40);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// === TAVANJE ===
|
||||||
|
z.state = 'wandering';
|
||||||
|
z.wanderTimer -= delta;
|
||||||
|
if (z.wanderTimer <= 0) {
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
z.wanderDirX = Math.cos(angle);
|
||||||
|
z.wanderDirY = Math.sin(angle);
|
||||||
|
z.wanderTimer = 2000 + Math.random() * 3000;
|
||||||
|
}
|
||||||
|
moveX = z.wanderDirX * speed * 0.5 * dt;
|
||||||
|
moveY = z.wanderDirY * speed * 0.5 * dt;
|
||||||
|
|
||||||
|
// Subtilen rdeči halo
|
||||||
|
z.halo.clear();
|
||||||
|
z.halo.fillStyle(0xff2200, 0.08);
|
||||||
|
z.halo.fillCircle(z.sprite.x, z.sprite.y - 20, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === PREMAKNI ===
|
||||||
|
z.sprite.x += moveX;
|
||||||
|
z.sprite.y += moveY;
|
||||||
|
z.halo.x = z.sprite.x - (z.halo.x || 0);
|
||||||
|
|
||||||
|
// Depth sorting
|
||||||
|
z.sprite.setDepth(z.sprite.y);
|
||||||
|
|
||||||
|
// === ANIMACIJA po smeri ===
|
||||||
|
const absX = Math.abs(moveX), absY = Math.abs(moveY);
|
||||||
|
let newDir = z.dir;
|
||||||
|
|
||||||
|
if (absX > 0.01 || absY > 0.01) {
|
||||||
|
if (absY > absX) {
|
||||||
|
newDir = moveY > 0 ? 'down' : 'up';
|
||||||
|
} else {
|
||||||
|
newDir = moveX > 0 ? 'right' : 'left';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDir !== z.dir) {
|
||||||
|
z.dir = newDir;
|
||||||
|
const animKey = `zombie_walk_${newDir}`;
|
||||||
|
if (z.sprite.anims.currentAnim?.key !== animKey) {
|
||||||
|
z.sprite.play(animKey, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otok bounds
|
||||||
|
z.sprite.x = Phaser.Math.Clamp(z.sprite.x, this.islandX, this.islandX + this.islandW);
|
||||||
|
z.sprite.y = Phaser.Math.Clamp(z.sprite.y, this.islandY, this.islandY + this.islandH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// ALFA MOČ — Udomačitev
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_tameZombie(zombie, kai) {
|
||||||
|
zombie.tamed = true;
|
||||||
|
zombie.state = 'tamed';
|
||||||
|
|
||||||
|
// Efekt: zlata eksplozija
|
||||||
|
this._alfaExplosion(zombie.sprite.x, zombie.sprite.y);
|
||||||
|
|
||||||
|
// Obarvi v zlato (shimmer)
|
||||||
|
zombie.sprite.setTint(0xffd700);
|
||||||
|
this.scene.time.delayedCall(800, () => {
|
||||||
|
zombie.sprite.clearTint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify scene
|
||||||
|
this.scene.events.emit('zombieTamed', { zombie });
|
||||||
|
|
||||||
|
this._showFloating(zombie.sprite.x, zombie.sprite.y - 60,
|
||||||
|
'✨ Udomačen!', '#ffd700', 22);
|
||||||
|
|
||||||
|
console.log(`✨ Zombi udomačen!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawAlfaBar(kai, zombie) {
|
||||||
|
const pct = this._alfaProgress / this.tamingTime;
|
||||||
|
const bx = zombie.sprite.x - 40;
|
||||||
|
const by = zombie.sprite.y - 110;
|
||||||
|
|
||||||
|
// Ozadje
|
||||||
|
this._alfaGfx.fillStyle(0x000000, 0.6);
|
||||||
|
this._alfaGfx.fillRect(bx, by, 80, 12);
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
this._alfaGfx.fillStyle(0xff4444, 1.0);
|
||||||
|
this._alfaGfx.fillRect(bx + 1, by + 1, (80 - 2) * pct, 10);
|
||||||
|
|
||||||
|
// Okvir
|
||||||
|
this._alfaGfx.lineStyle(1, 0xffffff, 0.8);
|
||||||
|
this._alfaGfx.strokeRect(bx, by, 80, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
_alfaExplosion(x, y) {
|
||||||
|
const gfx = this.scene.add.graphics().setDepth(9010);
|
||||||
|
let r = 5, alpha = 1.0;
|
||||||
|
const ev = this.scene.time.addEvent({
|
||||||
|
delay: 30,
|
||||||
|
repeat: 15,
|
||||||
|
callback: () => {
|
||||||
|
gfx.clear();
|
||||||
|
gfx.lineStyle(3, 0xffd700, alpha);
|
||||||
|
gfx.strokeCircle(x, y - 30, r);
|
||||||
|
gfx.strokeCircle(x, y - 30, r * 0.6);
|
||||||
|
r += 8; alpha -= 0.06;
|
||||||
|
if (r > 100) { gfx.destroy(); ev.remove(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zlatni prah
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const angle = (i / 8) * Math.PI * 2;
|
||||||
|
const particle = this.scene.add.graphics().setDepth(9011);
|
||||||
|
particle.fillStyle(0xffd700, 1.0);
|
||||||
|
particle.fillCircle(x, y - 30, 4);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: particle,
|
||||||
|
x: particle.x + Math.cos(angle) * 60,
|
||||||
|
y: particle.y + Math.sin(angle) * 60,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 600,
|
||||||
|
ease: 'Quad.out',
|
||||||
|
onComplete: () => particle.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// POMOŽNE
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
_getNearestHostile(kai) {
|
||||||
|
let nearest = null, minDist = this.alfaRange * 1.5;
|
||||||
|
for (const z of this.zombies) {
|
||||||
|
if (z.tamed) continue;
|
||||||
|
const d = Phaser.Math.Distance.Between(kai.x, kai.y, z.sprite.x, z.sprite.y);
|
||||||
|
if (d < minDist) { minDist = d; nearest = z; }
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
_showHint(x, y, msg, color) {
|
||||||
|
return this.scene.add.text(x, y, msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: '14px',
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
backgroundColor: '#00000088',
|
||||||
|
padding: { x: 6, y: 3 },
|
||||||
|
}).setOrigin(0.5).setDepth(9000);
|
||||||
|
}
|
||||||
|
|
||||||
|
_showFloating(x, y, msg, color, size = 20) {
|
||||||
|
const txt = this.scene.add.text(x, y, msg, {
|
||||||
|
fontFamily: 'Arial Black',
|
||||||
|
fontSize: `${size}px`,
|
||||||
|
color,
|
||||||
|
stroke: '#000000',
|
||||||
|
strokeThickness: 4,
|
||||||
|
}).setOrigin(0.5).setDepth(9999);
|
||||||
|
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: txt,
|
||||||
|
y: y - 60,
|
||||||
|
alpha: { from: 1, to: 0 },
|
||||||
|
duration: 1800,
|
||||||
|
onComplete: () => txt.destroy(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// PUBLIC API
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
getZombies() { return this.zombies; }
|
||||||
|
getTamedZombies() { return this.zombies.filter(z => z.tamed); }
|
||||||
|
getHostiles() { return this.zombies.filter(z => !z.tamed); }
|
||||||
|
|
||||||
|
removeAll() {
|
||||||
|
this.zombies.forEach(z => { z.sprite?.destroy(); z.halo?.destroy(); });
|
||||||
|
this.zombies = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.removeAll();
|
||||||
|
this.stopNightSpawning();
|
||||||
|
this._alfaGfx?.destroy();
|
||||||
|
this._spaceHint?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||