Compare commits
6 Commits
d3a93b6e7b
...
325ed52479
| Author | SHA1 | Date | |
|---|---|---|---|
| 325ed52479 | |||
| b660429e3c | |||
| 188bd769cf | |||
| 35153eeacf | |||
| 4cbb198d7a | |||
| 1d7aaf9562 |
BIN
assets/.DS_Store
vendored
BIN
assets/DEMO_FAZA1/.DS_Store
vendored
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
BIN
nova farma TRAE/assets/DEMO_FAZA1/Buildings/chest_closed.png
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
nova farma TRAE/assets/DEMO_FAZA1/Buildings/wooden_fence.png
Normal file
|
After Width: | Height: | Size: 298 KiB |
|
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 |
96
nova farma TRAE/dokumentacija/DEVLOG_2026_03_02.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 📓 Dev Log — 2026-03-02 (Ponedeljek)
|
||||
|
||||
**Seja:** Popoldanska / Večerna
|
||||
**Trajanje:** ~22 minut aktivnega dela
|
||||
**Git commit:** `4cbb198d7`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Opravljeno danes
|
||||
|
||||
### 1. 🌧️ Popravki dežnega sistema (Rain System)
|
||||
|
||||
**Problem:** Dež je padal samo na enem koncu mape (levi rob) in izgledal kot "bela eksplozija" — preveč gost, preveč bel.
|
||||
|
||||
**Rešitve (3 iteracije):**
|
||||
|
||||
| Iteracija | Problem | Rešitev |
|
||||
|-----------|---------|---------|
|
||||
| v1 | Dež samo levo | `min: 0, max: this.scale.width` → zamenjano z `startFollow(kai)` |
|
||||
| v2 | Bela eksplozija 😂 | `frequency: 10` preveč → zmanjšano na `frequency: 30`, `quantity: 3` |
|
||||
| v3 | Vzorci / stolpci kapljic | **Celotna zamenjava** sprite emitter → **procedural canvas** sistem |
|
||||
|
||||
**Končna implementacija (v3 — procedural):**
|
||||
- `160` naključnih kapljic kot kratke diagonalne črtice (8–18px)
|
||||
- Hitrost: 420–700 px/s (hiter, naraven)
|
||||
- Alpha: 0.25–0.55 (prozorno — otok in Kai vedno vidna)
|
||||
- Pravi `Math.random()` razmestitev — brez vzorcev ali "stebrov"
|
||||
- Črtice so svetlo modre (`0xaaddff`) z rahlim nagibom v levo
|
||||
- Vsaka kapljica se ob izhodu iz zaslona teleportira nazaj na vrh (recycling)
|
||||
|
||||
**Bug fix:** `SyntaxError: Identifier 'viewW' has already been declared`
|
||||
→ Preimenovano v `rainViewW` / `rainViewH`
|
||||
|
||||
---
|
||||
|
||||
### 2. 🏗️ Building System (`src/systems/BuildingSystem.js`)
|
||||
|
||||
**Nov modul** za postavljanje stavb na otok.
|
||||
|
||||
**Funkcionalnosti:**
|
||||
|
||||
- **[B]** tipka — odpre/zapre Build Mode
|
||||
- **[Esc]** — izhod iz Build Mode
|
||||
- **Stranski panel** (desno) z ikonami stavb:
|
||||
- 🏕️ Šotor (`sotor`)
|
||||
- 🔥 Taborni ogenj (`campfire`)
|
||||
- 💧 Zbirač dežja (`rain_catcher`)
|
||||
- 🏗️ Betonski temelj (`foundation_concrete`)
|
||||
- **Ghost preview** — slika sledi mišu
|
||||
- Zelena = veljavna postavitev (na otoku)
|
||||
- Rdeča = neveljavna (zunaj otoka / v oceanu)
|
||||
- **Klik = postavi** stavbo s fizičnim colliderjem
|
||||
- **Y-sorting** — Kai se skrije za stavbo ko gre zadaj, pride pred njo ko gre spredaj
|
||||
- **Static physics body** — Kai ne more hoditi skozi stavbe
|
||||
- **Spawn animacija** `Back.out` (stavba "potrdi" pozicijo)
|
||||
|
||||
**Merjenje:** Stavbe skalirajo glede na Kai višino (64px):
|
||||
- Šotor = 2.5× Kai = 160px
|
||||
- Zbirač dežja = 2.0× Kai = 128px
|
||||
- Temelj = 2.0× Kai = 128px
|
||||
- Ogenj = 1.0× Kai = 64px
|
||||
|
||||
---
|
||||
|
||||
## 🚧 Znane omejitve / Za naslednjič
|
||||
|
||||
- [ ] Stavbe se ne shranjujejo med sejami (ni persistence/save sistema)
|
||||
- [ ] Building panel je fiksiran na desni — ne upošteva zoom-a kamere
|
||||
- [ ] Šotor in ogenj nima animacij (campfire bi moral goreti)
|
||||
- [ ] Nima `Demolish` (rušenje postavljenih stavb)
|
||||
- [ ] Zvočni efekti ob postavitvi stavb (placeholder potreben)
|
||||
- [ ] `foundation_concrete` assetov je malo — potrebno generirati hiše/kmečke zgradbe
|
||||
|
||||
---
|
||||
|
||||
## 💡 Ideje za naslednjo sejo
|
||||
|
||||
1. **Save/Load sistem** — shrani placed buildings v `localStorage` → ohrani stanje med restart-om
|
||||
2. **Campfire animacija** — flickering light z Graphics ali sprite sheet
|
||||
3. **Hiša** — generirati asset `hisa_osnovna.png` in dodati v BuildingSystem katalog
|
||||
4. **Rušenje stavb** — desni klik = odstranjuje zgradbo v build modu
|
||||
5. **Build Grid** — opcijsko prikaži mrežo pri gradnji za lažje poravnavanje
|
||||
6. **Zvok dežja** — proceduralni Web Audio API šum (beli šum + filter)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Stanje projekta
|
||||
|
||||
**Git:** `master` — 2 commita pred origin
|
||||
**Zmogljivost:** Scena deluje brez performance problemov (160 rain drops @ 60fps ✅)
|
||||
**Faza:** Demo Faza 1 — aktivni razvoj
|
||||
|
||||
---
|
||||
|
||||
*Dev log zapisan: 2026-03-02 ob 20:42*
|
||||
*Naslednja seja: TBD*
|
||||
@@ -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`*
|
||||
97
nova farma TRAE/dokumentacija/TODO_NEXT_SESSION.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 📋 TODO - Next Session (11.2.2026+)
|
||||
|
||||
**Last Session:** 10-11.2.2026 (6 hours)
|
||||
**Progress:** 23/160 assets (14.4%)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PRIORITETE ZA NASLEDNJI SESSION:
|
||||
|
||||
### **1. BATCH 2 - KAI ANIMATIONS (2 Missing)**
|
||||
- [ ] `kai_death_sheet.png` - Death animation (4-6 frames)
|
||||
- [ ] `kai_tool_use_sheet.png` - Tool animations (hoe, axe, water)
|
||||
|
||||
**Reference:** `/assets/references/kaj.png` (correct DNA!)
|
||||
|
||||
---
|
||||
|
||||
### **2. BATCH 11 - STRUCTURES (Missing Assets)**
|
||||
|
||||
#### **MANJKA BARN/SKEDENJ:**
|
||||
- [ ] Generate `barn_ruined.png` - Ruined state
|
||||
- [ ] Generate `barn_repaired.png` - Fixed state
|
||||
|
||||
**Important:**
|
||||
- Reference `skedenj.png` NE OBSTAJA!
|
||||
- Moraš generirati iz opisa
|
||||
- Gothic chibi style
|
||||
- Dark wood, broken roof (ruined)
|
||||
- Fixed roof, cleaner (repaired)
|
||||
|
||||
**Specifications:**
|
||||
- Animal housing (4 animals capacity)
|
||||
- Storage functionality
|
||||
- Repair cost: 60 Wood, 25 Stone, 5 Iron
|
||||
- Repair time: 3 days
|
||||
|
||||
---
|
||||
|
||||
### **3. BATCH 4 - POLAROID INTRO (8 Images)**
|
||||
|
||||
Critical for narrative intro:
|
||||
- [ ] `polaroid_13.png` - Kai as child
|
||||
- [ ] `polaroid_14.png` - Family photo
|
||||
- [ ] `polaroid_15.png` - Teenage Kai
|
||||
- [ ] `polaroid_16.png` - Ana closeup
|
||||
- [ ] `polaroid_17.png` - Cannabis plant memory
|
||||
- [ ] `polaroid_18.png` - Abandoned island arrival
|
||||
- [ ] `polaroid_19.png` - Kai + Gronk first meeting
|
||||
- [ ] `polaroid_20.png` - Valley of Death landscape
|
||||
|
||||
**Style:** Polaroid aesthetic, faded colors, white frame borders
|
||||
|
||||
---
|
||||
|
||||
### **4. TECHNICAL FIXES**
|
||||
|
||||
- [ ] Test rain system in browser (refresh + verify)
|
||||
- [ ] Re-enable vegetation system (uncomment lines 1027-1048)
|
||||
- [ ] Add toggle for rain (T key or UI button?)
|
||||
|
||||
---
|
||||
|
||||
## ✅ ŠE COMPLETED (Session 10-11.2):
|
||||
|
||||
**Zombies:** 7 characters + 2 references ✅
|
||||
**Kai:** 4 animations (idle, walk, attack) ✅
|
||||
**Environment:** 9 assets (sky, sun, moon, rain) ✅
|
||||
**Code:** Rain weather system ✅
|
||||
**Docs:** MASTER_GAME_BIBLE + DNEVNIK updated ✅
|
||||
|
||||
---
|
||||
|
||||
## 📊 BATCH STATUS:
|
||||
|
||||
- ✅ **Batch 1 (Zombies): 100%** (8/8)
|
||||
- ⏸️ **Batch 2 (Kai): 67%** (4/6)
|
||||
- ✅ **Batch 3 (Environment): 100%** (7/7 + rain)
|
||||
- ⏸️ **Batch 4 (Polaroid): 0%** (0/8)
|
||||
- ⏸️ **Batch 11 (Structures): 60%** (missing barn)
|
||||
|
||||
---
|
||||
|
||||
## 🎮 GAME STATE:
|
||||
|
||||
- Rain system: ✅ Active
|
||||
- Vegetation: 🚫 Disabled (testing)
|
||||
- Assets: All in correct directories
|
||||
- Commits: All saved
|
||||
|
||||
---
|
||||
|
||||
**NEXT SESSION START HERE! 🚀**
|
||||
|
||||
Datum: 11.2.2026+
|
||||
Focus: Kai animations → Barn → Polaroid intro
|
||||
|
||||
---
|
||||
@@ -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`*
|
||||
@@ -4,6 +4,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Nova Farma - Clean Start</title>
|
||||
<!-- Google Fonts za UTF-8 in šumnike -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
97
nova farma TRAE/public/assets/localization.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"sl": {
|
||||
"menu_play": "IGRAJ",
|
||||
"menu_options": "NASTAVITVE",
|
||||
"menu_exit": "IZHOD",
|
||||
"lang_btn": "JEZIK: SL",
|
||||
"acc_menu": "MENI ZA DOSTOPNOST",
|
||||
"acc_deaf": "🔊 Vizualna Opozorila (Deaf Mode)",
|
||||
"acc_autism": "⚡ Senzorni Način (Reduce Motion)",
|
||||
"acc_adhd": "🎯 Zen/ADHD Način (Fokus Cilja)",
|
||||
"acc_font": "🔎 Povečana Pisava",
|
||||
"acc_contrast": "🔲 Visok Kontrast",
|
||||
"acc_colorblind": "👁️ Filter Barvne Slepote",
|
||||
"acc_onehand": "🕹️ Enoročni Način",
|
||||
"acc_close": "[ X ZAPRI ]",
|
||||
"day": "DAN",
|
||||
"map": "MAPA",
|
||||
"crafting": "CRAFTING",
|
||||
"adhd_goal": "⬇️ TVOJ TRENUTNI CILJ: ZALIJ PŠENICO! ⬇️"
|
||||
},
|
||||
"en": {
|
||||
"menu_play": "PLAY",
|
||||
"menu_options": "OPTIONS",
|
||||
"menu_exit": "EXIT",
|
||||
"lang_btn": "LANGUAGE: EN",
|
||||
"acc_menu": "ACCESSIBILITY MENU",
|
||||
"acc_deaf": "🔊 Visual Alerts (Deaf Mode)",
|
||||
"acc_autism": "⚡ Sensory Mode (Reduce Motion)",
|
||||
"acc_adhd": "🎯 Zen/ADHD Mode (Goal Focus)",
|
||||
"acc_font": "🔎 Large Font",
|
||||
"acc_contrast": "🔲 High Contrast",
|
||||
"acc_colorblind": "👁️ Colorblind Filter",
|
||||
"acc_onehand": "🕹️ One-handed Mode",
|
||||
"acc_close": "[ X CLOSE ]",
|
||||
"day": "DAY",
|
||||
"map": "MAP",
|
||||
"crafting": "CRAFTING",
|
||||
"adhd_goal": "⬇️ YOUR CURRENT GOAL: WATER THE WHEAT! ⬇️"
|
||||
},
|
||||
"de": {
|
||||
"menu_play": "SPIELEN",
|
||||
"menu_options": "OPTIONEN",
|
||||
"menu_exit": "BEENDEN",
|
||||
"lang_btn": "SPRACHE: DE",
|
||||
"acc_menu": "BARRIEREFREIHEITS-MENÜ",
|
||||
"acc_deaf": "🔊 Visuelle Alarme (Gehörlosenmodus)",
|
||||
"acc_autism": "⚡ Sensibler Modus (Weniger Bewegung)",
|
||||
"acc_adhd": "🎯 Zen/ADHD-Modus (Zielfokus)",
|
||||
"acc_font": "🔎 Große Schrift",
|
||||
"acc_contrast": "🔲 Hoher Kontrast",
|
||||
"acc_colorblind": "👁️ Farbenblind-Filter",
|
||||
"acc_onehand": "🕹️ Einhandmodus",
|
||||
"acc_close": "[ X SCHLIESSEN ]",
|
||||
"day": "TAG",
|
||||
"map": "KARTE",
|
||||
"crafting": "HERSTELLUNG",
|
||||
"adhd_goal": "⬇️ DEIN AKTUELLES ZIEL: WEIZEN GIESSEN! ⬇️"
|
||||
},
|
||||
"it": {
|
||||
"menu_play": "GIOCA",
|
||||
"menu_options": "OPZIONI",
|
||||
"menu_exit": "ESCI",
|
||||
"lang_btn": "LINGUA: IT",
|
||||
"acc_menu": "MENU ACCESSIBILITÀ",
|
||||
"acc_deaf": "🔊 Avvisi Visivi (Modalità Sordi)",
|
||||
"acc_autism": "⚡ Modalità Sensoriale (Riduci Movimento)",
|
||||
"acc_adhd": "🎯 Modalità Zen/ADHD (Focus Obiettivo)",
|
||||
"acc_font": "🔎 Carattere Grande",
|
||||
"acc_contrast": "🔲 Alto Contrasto",
|
||||
"acc_colorblind": "👁️ Filtro Daltonismo",
|
||||
"acc_onehand": "🕹️ Modalità Una Mano",
|
||||
"acc_close": "[ X CHIUDI ]",
|
||||
"day": "GIORNO",
|
||||
"map": "MAPPA",
|
||||
"crafting": "CREAZIONE",
|
||||
"adhd_goal": "⬇️ IL TUO OBIETTIVO ATTUALE: ANNAFFIA IL GRANO! ⬇️"
|
||||
},
|
||||
"cn": {
|
||||
"menu_play": "开始游戏",
|
||||
"menu_options": "设置",
|
||||
"menu_exit": "退出",
|
||||
"lang_btn": "语言: CN",
|
||||
"acc_menu": "无障碍菜单",
|
||||
"acc_deaf": "🔊 视觉警报 (聋人模式)",
|
||||
"acc_autism": "⚡ 感觉模式 (减少动画)",
|
||||
"acc_adhd": "🎯 禅/ADHD模式 (目标专注)",
|
||||
"acc_font": "🔎 大字体",
|
||||
"acc_contrast": "🔲 高对比度",
|
||||
"acc_colorblind": "👁️ 色盲滤镜",
|
||||
"acc_onehand": "🕹️ 单手模式",
|
||||
"acc_close": "[ X 关闭 ]",
|
||||
"day": "天",
|
||||
"map": "地图",
|
||||
"crafting": "制作",
|
||||
"adhd_goal": "⬇️ 你当前的目标:给小麦浇水! ⬇️"
|
||||
}
|
||||
}
|
||||
25
nova farma TRAE/remove_bg.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import sys
|
||||
from PIL import Image
|
||||
|
||||
def process_image(input_path, output_path):
|
||||
img = Image.open(input_path).convert("RGBA")
|
||||
datas = img.getdata()
|
||||
|
||||
newData = []
|
||||
# Convert white background to transparent
|
||||
for item in datas:
|
||||
# Check if the pixel color is white or close to white (tolerance)
|
||||
if item[0] >= 240 and item[1] >= 240 and item[2] >= 240:
|
||||
newData.append((255, 255, 255, 0)) # Replace with transparent
|
||||
else:
|
||||
newData.append(item)
|
||||
|
||||
img.putdata(newData)
|
||||
img.save(output_path, "PNG")
|
||||
print(f"Saved {output_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python remove_bg.py <input> <output>")
|
||||
else:
|
||||
process_image(sys.argv[1], sys.argv[2])
|
||||
@@ -3,6 +3,7 @@ if (typeof process !== 'undefined' && process.env) {
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
}
|
||||
|
||||
import MenuScene from './scenes/MenuScene.js';
|
||||
import GrassScene from './scenes/GrassScene_Clean.js';
|
||||
import UIScene from './scenes/UIScene.js';
|
||||
|
||||
@@ -14,8 +15,8 @@ const config = {
|
||||
mode: Phaser.Scale.FIT,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH
|
||||
},
|
||||
pixelArt: true, // Zagotovi ostrino pri zumiranju (pixel art style)
|
||||
backgroundColor: '#1a1a1a', // Temno siva, da slika izstopa
|
||||
pixelArt: false, // Meni uporablja smooth slike
|
||||
backgroundColor: '#000000',
|
||||
parent: 'body',
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
@@ -24,7 +25,9 @@ const config = {
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
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';
|
||||
import TwinBondSystem from '../systems/TwinBondSystem.js';
|
||||
import JournalSystem from '../systems/JournalSystem.js';
|
||||
|
||||
export default class GrassSceneClean extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'GrassSceneClean' });
|
||||
@@ -20,6 +29,26 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.LAYER_TREES = 2; // Trees
|
||||
this.LAYER_CHARACTERS = 10; // Kai, zombies
|
||||
|
||||
// === PIXEL-PERFECT SCALE SYSTEM ===
|
||||
// Base tile reference: 32x32 px
|
||||
// All objects scale to exact pixel sizes on screen
|
||||
this.PIXEL_TILE = 32;
|
||||
this.PIXEL_KAI_HEIGHT = 64; // 2 tiles
|
||||
this.PIXEL_GRASS_LOW = 10; // Max height for low grass
|
||||
this.PIXEL_GRASS_TALL_MIN = 24;
|
||||
this.PIXEL_GRASS_TALL_MAX = 28;
|
||||
this.PIXEL_GRASS_TALL_AVG = 28; // UPDATED: Target 28px (user spec)
|
||||
this.PIXEL_OAK_WIDTH = 96; // 3 tiles
|
||||
this.PIXEL_OAK_HEIGHT = 160; // 5 tiles
|
||||
|
||||
// ROOT OFFSET: Bury plants 6px into ground
|
||||
this.ROOT_OFFSET = 6; // Plants sink into brown dirt texture
|
||||
|
||||
// Asset source dimensions (for scale calculation)
|
||||
this.SOURCE_KAI_FRAME = 256; // kai_walk_sheet frame size
|
||||
this.SOURCE_GRASS_TALL = 1024; // visoka_trava_v2.png
|
||||
this.SOURCE_TREE_AVG = 366; // tree_adult average height
|
||||
|
||||
// === GROWTH STATE ===
|
||||
this.soilGrid = []; // 50x50 grid for moisture tracking
|
||||
this.grassTiles = []; // Dynamic grass instances
|
||||
@@ -90,6 +119,7 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.load.image('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png');
|
||||
this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png');
|
||||
this.load.image('sleeping_bag', 'DEMO_FAZA1/Items/spalna_vreca.png');
|
||||
this.load.image('chest_closed', 'DEMO_FAZA1/Buildings/chest_closed.png');
|
||||
|
||||
// 7. NEW: Gronk & Structures
|
||||
this.load.spritesheet('gronk', 'DEMO_FAZA1/Characters/gronk_walk_sheet.png', {
|
||||
@@ -97,9 +127,117 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
frameHeight: 256
|
||||
});
|
||||
this.load.image('rain_catcher', 'DEMO_FAZA1/Structures/rain_catcher.png');
|
||||
this.load.image('foundation_concrete', 'DEMO_FAZA1/Structures/foundation_concrete.png');
|
||||
|
||||
// 8. Weather System
|
||||
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);
|
||||
|
||||
// 12. Building System Assets
|
||||
this.load.image('wooden_fence', 'DEMO_FAZA1/Buildings/wooden_fence.png');
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// AUTO-LOADER SYSTEM: Vegetation & Trees
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* MAP_LOADER: Auto-load vegetation with pixel-perfect grounding
|
||||
* @param {number} x - World X position
|
||||
* @param {number} y - World Y position (tile bottom)
|
||||
* @param {string} key - Asset key from Vegetation folder
|
||||
* @returns {Phaser.GameObjects.Image} Grounded vegetation sprite
|
||||
*/
|
||||
loadVegetation(x, y, key) {
|
||||
// Get source dimensions
|
||||
const texture = this.textures.get(key);
|
||||
const sourceHeight = texture.getSourceImage().height;
|
||||
|
||||
// VEGETATION SPEC: 28px final height, 4px burial
|
||||
const targetHeight = this.PIXEL_GRASS_TALL_AVG; // 28px
|
||||
const burialOffset = 4; // Vegetation burial depth
|
||||
|
||||
// Calculate scale
|
||||
const scale = targetHeight / sourceHeight;
|
||||
|
||||
// Create sprite
|
||||
const sprite = this.add.image(x, y + burialOffset, key)
|
||||
.setScale(scale)
|
||||
.setOrigin(0.5, 1.0) // Bottom-center anchor
|
||||
.setDepth(y + burialOffset); // Y-sorting
|
||||
|
||||
// SMOOTH RENDERING: Linear filter (no pixelation)
|
||||
sprite.texture.setFilter(Phaser.Textures.FilterMode.LINEAR);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
/**
|
||||
* TREE_LOADER: Auto-load trees with pixel-perfect grounding
|
||||
* @param {number} x - World X position
|
||||
* @param {number} y - World Y position (tile bottom)
|
||||
* @param {string} key - Asset key from Trees folder
|
||||
* @returns {Phaser.GameObjects.Image} Grounded tree sprite
|
||||
*/
|
||||
loadTree(x, y, key) {
|
||||
// Get source dimensions
|
||||
const texture = this.textures.get(key);
|
||||
const sourceHeight = texture.getSourceImage().height;
|
||||
|
||||
// TREE SPEC: 160px final height, 8px burial
|
||||
const targetHeight = this.PIXEL_OAK_HEIGHT; // 160px
|
||||
const burialOffset = 8; // Tree burial depth
|
||||
|
||||
// Calculate scale
|
||||
const scale = targetHeight / sourceHeight;
|
||||
|
||||
// Create sprite
|
||||
const sprite = this.add.image(x, y + burialOffset, key)
|
||||
.setScale(scale)
|
||||
.setOrigin(0.5, 1.0) // Bottom-center anchor
|
||||
.setDepth(y + burialOffset); // Y-sorting
|
||||
|
||||
// SMOOTH RENDERING: Linear filter (no pixelation)
|
||||
sprite.texture.setFilter(Phaser.Textures.FilterMode.LINEAR);
|
||||
|
||||
// PHYSICS & INTERACTION
|
||||
this.physics.add.existing(sprite, true); // true = static body
|
||||
if (sprite.body) {
|
||||
// Trunk size roughly matching the visual base
|
||||
sprite.body.setSize(sprite.width * scale * 0.4, 40);
|
||||
sprite.body.setOffset(sprite.width * 0.3, sprite.height - 40);
|
||||
}
|
||||
|
||||
sprite.health = 3;
|
||||
sprite.isChopping = false;
|
||||
sprite.setInteractive({ useHandCursor: true });
|
||||
sprite.on('pointerdown', () => {
|
||||
const dist = Phaser.Math.Distance.Between(this.scene?.kai?.x || this.kai?.x, this.scene?.kai?.y || this.kai?.y, sprite.x, sprite.y);
|
||||
|
||||
if (dist < 150) {
|
||||
// Preveri če ima igralec izbrano sekiro v rokah
|
||||
const activeItemKey = this.inventorySystem?.hotbar[this.inventorySystem?.activeSlot];
|
||||
const activeItemDef = activeItemKey ? this.inventorySystem?.ITEM_DEFS[activeItemKey] : null;
|
||||
|
||||
if (activeItemDef && activeItemDef.tool === 'axe') {
|
||||
this.chopTree(sprite);
|
||||
} else {
|
||||
if (this.inventorySystem) {
|
||||
this.inventorySystem._showMsg('Rabiš Sekiro v rokah!', '#ff4444');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
create() {
|
||||
@@ -155,12 +293,12 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.cameras.main.setBackgroundColor('#001d3d'); // Deep Blue Ocean
|
||||
|
||||
// --- ZOOM CONTROL ---
|
||||
this.cameras.main.setZoom(0.8);
|
||||
this.cameras.main.setZoom(1.0); // Privzet zoom za Demo
|
||||
|
||||
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
|
||||
const zoomFactor = -0.001;
|
||||
const newZoom = this.cameras.main.zoom + deltaY * zoomFactor;
|
||||
this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.2, 5.0));
|
||||
this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.5, 2.5)); // Omejitev 0.5 do 2.5
|
||||
});
|
||||
|
||||
// --- 1. DYNAMIC OCEAN & ISLAND SYSTEM ---
|
||||
@@ -254,17 +392,13 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
|
||||
// FAILSAFE 2: Use simple Rectangles instead of TileSprites/Textures to STOP OOM.
|
||||
// We will add waves later if stability allows.
|
||||
const viewW = this.scale.width || 1280;
|
||||
const viewH = this.scale.height || 720;
|
||||
|
||||
this.oceanLayer1 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x004488)
|
||||
.setScrollFactor(0)
|
||||
// Ustvarimo dva velika modra pravokotnika kot 'bazen', v katerem je otok
|
||||
this.oceanLayer1 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x004488)
|
||||
.setDepth(-290)
|
||||
.setAlpha(0.5);
|
||||
|
||||
// Optional: Second layer for depth effect
|
||||
this.oceanLayer2 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x002244)
|
||||
.setScrollFactor(0)
|
||||
this.oceanLayer2 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x002244)
|
||||
.setDepth(-295) // Behind
|
||||
.setAlpha(1.0);
|
||||
|
||||
@@ -540,7 +674,9 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
/*
|
||||
// Trnje (Thorns) - Draggable
|
||||
this.trnje = this.add.image(startX - 200, startY + 100, 'trnje');
|
||||
this.trnje.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
|
||||
this.trnje.setScale(0.5); // Adjust scale if needed
|
||||
this.trnje.setDepth(this.trnje.y); // Y-sorting
|
||||
this.trnje.setInteractive({ draggable: true });
|
||||
// Trigger Amnesia Clear on interaction
|
||||
this.trnje.on('pointerdown', () => {
|
||||
@@ -549,9 +685,10 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
|
||||
// --- NEW: RAIN CATCHER ---
|
||||
this.rainCatcher = this.physics.add.image(startX + 150, startY + 50, 'rain_catcher');
|
||||
this.rainCatcher.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
|
||||
this.rainCatcher.setScale(0.8);
|
||||
this.rainCatcher.setInteractive({ draggable: true });
|
||||
this.rainCatcher.setDepth(startY + 50); // Y-sort
|
||||
this.rainCatcher.setDepth(this.rainCatcher.y); // Y-sort
|
||||
this.rainCatcher.body.setImmovable(true);
|
||||
// Collider added later with Kai
|
||||
*/
|
||||
@@ -826,10 +963,19 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
const SPAWN_Y = WORLD_H / 2;
|
||||
|
||||
this.kai = this.physics.add.sprite(SPAWN_X, SPAWN_Y, 'kai');
|
||||
// Povečava na polno velikost (256px)
|
||||
this.kai.setScale(1);
|
||||
|
||||
// PIXEL-PERFECT SCALE: Kai = 64px height (2 tiles)
|
||||
// Source frame: 256x256 → Target: 64px
|
||||
const kaiScale = this.PIXEL_KAI_HEIGHT / this.SOURCE_KAI_FRAME;
|
||||
this.kai.setScale(kaiScale); // 0.25x → 64px exact
|
||||
|
||||
this.kai.setCollideWorldBounds(true);
|
||||
this.kai.setOrigin(0.5, 0.9);
|
||||
|
||||
// ANCHOR POINT: Bottom-center (feet on ground)
|
||||
this.kai.setOrigin(0.5, 1.0);
|
||||
|
||||
// DYNAMIC DEPTH: Y-sorting for walking between vegetation
|
||||
this.kai.setDepth(this.kai.y);
|
||||
|
||||
// RESPAWN SYSTEM
|
||||
this.respawnPoint = { x: SPAWN_X, y: SPAWN_Y };
|
||||
@@ -848,12 +994,109 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.physics.add.overlap(this.kai, this.sleepingBag, () => {
|
||||
if (this.lastCheckpointTime && this.time.now - this.lastCheckpointTime < 5000) return;
|
||||
|
||||
this.saveCheckpoint(this.sleepingBag.x, this.sleepingBag.y);
|
||||
this.lastCheckpointTime = this.time.now;
|
||||
});
|
||||
*/
|
||||
console.log('SPALNA VREČA ODSTRANJENA');
|
||||
// ─── HARDCODED DEMO BASE (8x8) ────────────────────────
|
||||
// Zgoraj Levo: Šotor (2x2)
|
||||
this.sotor = this.physics.add.image(SPAWN_X - 128, SPAWN_Y - 128, 'sotor');
|
||||
this.sotor.setOrigin(0.5, 1.0);
|
||||
this.sotor.setScale(2.5); // iz BuildingSystem.js
|
||||
this.sotor.setDepth(this.sotor.y);
|
||||
this.sotor.body.setSize(160, 40);
|
||||
this.sotor.body.setOffset(-80, -40);
|
||||
this.sotor.body.setImmovable(true);
|
||||
this.sleepingBag = this.sotor; // Uporabi šotor kot checkpoint za spanje
|
||||
|
||||
// Sredina: Taborni ogenj (1x1)
|
||||
this.campfire = this.physics.add.image(SPAWN_X - 32, SPAWN_Y - 32, 'campfire');
|
||||
this.campfire.setOrigin(0.5, 1.0);
|
||||
this.campfire.setDepth(this.campfire.y);
|
||||
this.campfire.body.setSize(50, 20);
|
||||
this.campfire.body.setOffset(25, 30);
|
||||
this.campfire.body.setImmovable(true);
|
||||
|
||||
// Sredina: Skrinja
|
||||
this.starterChest = this.physics.add.image(SPAWN_X + 64, SPAWN_Y - 32, 'chest_closed');
|
||||
this.starterChest.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
|
||||
this.starterChest.setScale(0.18);
|
||||
this.starterChest.setDepth(this.starterChest.y); // Y-sorting
|
||||
this.starterChest.body.setSize(this.starterChest.width * 0.18, this.starterChest.height * 0.18);
|
||||
this.starterChest.body.setImmovable(true);
|
||||
this.starterChest.opened = false;
|
||||
|
||||
let chestText = this.add.text(this.starterChest.x, this.starterChest.y - 50, '[E] Odpri Kišto', { fontFamily: 'Arial Black', fontSize: '13px', color: '#ffdd00', stroke: '#000', strokeThickness: 4, align: 'center' }).setOrigin(0.5, 0.5);
|
||||
chestText.setDepth(9999);
|
||||
|
||||
this.input.keyboard.on('keydown-E', () => {
|
||||
if (this.starterChest.opened) return;
|
||||
const dist = Phaser.Math.Distance.Between(this.kai.x, this.kai.y, this.starterChest.x, this.starterChest.y);
|
||||
if (dist < 150) {
|
||||
this.starterChest.opened = true;
|
||||
chestText.setText('📦 Prazno\n(Vse pobrano)');
|
||||
chestText.setColor('#888888');
|
||||
|
||||
this.tweens.add({
|
||||
targets: this.starterChest,
|
||||
y: this.starterChest.y - 20,
|
||||
yoyo: true,
|
||||
duration: 150,
|
||||
onComplete: () => {
|
||||
this.starterChest.setTint(0x888888);
|
||||
}
|
||||
});
|
||||
|
||||
// Add items directly to Inventory
|
||||
this.inventorySystem.addItem('axe', 1);
|
||||
this.inventorySystem.addItem('hoe', 1);
|
||||
this.inventorySystem.addItem('watering_can', 1);
|
||||
this.inventorySystem.addItem('wheat_seed', 5);
|
||||
this.inventorySystem.addItem('carrot_seed', 5);
|
||||
|
||||
// UI Message
|
||||
this.inventorySystem._showMsg('Pobral si Orodje in Semena!', '#44ff44');
|
||||
if (this.journalSystem) this.journalSystem.unlockEntry('start');
|
||||
}
|
||||
});
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// === NEPREBOJEN GOZD (IMPENETRABLE FOREST WALL) ===
|
||||
this.treesGroup = this.physics.add.staticGroup();
|
||||
|
||||
const treeKeys = ['tree_adult_0', 'tree_adult_1', 'tree_adult_2', 'tree_adult_3', 'tree_adult_4', 'tree_adult_5'];
|
||||
const TREE_RADIUS = 400; // Velikost 8x8 cone
|
||||
|
||||
// Inner dense circle
|
||||
for (let angle = 0; angle < Math.PI * 2; angle += 0.25) {
|
||||
let tx = SPAWN_X + Math.cos(angle) * TREE_RADIUS + (Math.random() - 0.5) * 60;
|
||||
let ty = SPAWN_Y + Math.sin(angle) * TREE_RADIUS + (Math.random() - 0.5) * 60;
|
||||
let tKey = Phaser.Utils.Array.GetRandom(treeKeys);
|
||||
let t = this.loadTree(tx, ty, tKey);
|
||||
if(t) this.treesGroup.add(t);
|
||||
}
|
||||
|
||||
// Outer dense circle
|
||||
for (let angle = 0; angle < Math.PI * 2; angle += 0.15) {
|
||||
let tx = SPAWN_X + Math.cos(angle) * (TREE_RADIUS + 120) + (Math.random() - 0.5) * 80;
|
||||
let ty = SPAWN_Y + Math.sin(angle) * (TREE_RADIUS + 120) + (Math.random() - 0.5) * 80;
|
||||
let tKey = Phaser.Utils.Array.GetRandom(treeKeys);
|
||||
let t = this.loadTree(tx, ty, tKey);
|
||||
if(t) this.treesGroup.add(t);
|
||||
}
|
||||
|
||||
// === FOG OF WAR (Temna megla izven baze) ===
|
||||
this.fowGraphics = this.add.graphics();
|
||||
this.fowGraphics.setDepth(6000);
|
||||
this.fowGraphics.fillStyle(0x0a0f12, 0.98); // Zelo temno modro-siva megla
|
||||
|
||||
// Narišemo 4 pravokotnike, ki pustijo luknjo na sredini
|
||||
const FOW_R = TREE_RADIUS + 60;
|
||||
this.fowGraphics.fillRect(0, 0, WORLD_W, SPAWN_Y - FOW_R); // Zgoraj
|
||||
this.fowGraphics.fillRect(0, SPAWN_Y + FOW_R, WORLD_W, WORLD_H); // Spodaj
|
||||
this.fowGraphics.fillRect(0, SPAWN_Y - FOW_R, SPAWN_X - FOW_R, FOW_R * 2); // Levo
|
||||
this.fowGraphics.fillRect(SPAWN_X + FOW_R, SPAWN_Y - FOW_R, WORLD_W, FOW_R * 2); // Desno
|
||||
|
||||
// Add solid collision against trees!
|
||||
this.physics.add.collider(this.kai, this.treesGroup);
|
||||
// ===================================================================
|
||||
|
||||
// Adjust Physics Body for larger size
|
||||
// Width ~40, Height ~30 (relative to scaled sprite)
|
||||
@@ -867,8 +1110,9 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
// REMOVED PER USER REQUEST
|
||||
/*
|
||||
this.gronk = this.physics.add.sprite(startX - 150, startY - 100, 'gronk');
|
||||
this.gronk.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
|
||||
this.gronk.setScale(0.8); // Gronk is big!
|
||||
this.gronk.setDepth(startY - 100);
|
||||
this.gronk.setDepth(this.gronk.y); // Y-sorting
|
||||
this.gronk.setImmovable(true);
|
||||
this.gronk.body.setSize(80, 60);
|
||||
this.gronk.body.setOffset(88, 190); // Adjusted for 256x256 frame
|
||||
@@ -925,8 +1169,11 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.kai.stop();
|
||||
|
||||
// Camera setup logic
|
||||
this.cameras.main.startFollow(this.kai, true, 0.1, 0.1);
|
||||
this.cameras.main.startFollow(this.kai, true, 0.08, 0.08); // Zelo mehko sledenje
|
||||
this.cameras.main.setRoundPixels(true); // Prevent jitter
|
||||
|
||||
// Spremenljivka za gladko drsenje kamere naprej
|
||||
this.cameraLookahead = { x: 0, y: 0 };
|
||||
this.cursors = this.input.keyboard.createCursorKeys();
|
||||
// Add WASD keys
|
||||
this.keys = this.input.keyboard.addKeys({
|
||||
@@ -957,6 +1204,27 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
if (this.riverCollider) this.physics.add.collider(this.kai, this.riverCollider);
|
||||
// this.physics.add.collider(this.kai, this.obstaclesGroup);
|
||||
|
||||
// === BUILDING SYSTEM ===
|
||||
this.buildingSystem = new BuildingSystem(this, this.kai);
|
||||
|
||||
// Click-to-place: only fire in world (not on UI panel area)
|
||||
this.input.on('pointerdown', (pointer) => {
|
||||
if (!this.buildingSystem.active) return;
|
||||
// Ignore right-side panel area (screen-space x > 85% of width)
|
||||
if (pointer.x > this.cameras.main.width - 100) return;
|
||||
this.buildingSystem.placeBuilding(pointer.worldX, pointer.worldY);
|
||||
});
|
||||
|
||||
console.log('🏗️ Building System initialized — press [B] to open build mode');
|
||||
|
||||
// === JOURNAL SYSTEM (Kaijev Dnevnik) ===
|
||||
this.journalSystem = new JournalSystem(this);
|
||||
|
||||
// === ZAŽENIMO UIScene kot overlay (HUD sloj) ===
|
||||
if (!this.scene.isActive('UIScene')) {
|
||||
this.scene.launch('UIScene');
|
||||
}
|
||||
|
||||
// --- ANIMATIONS ---
|
||||
// 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up
|
||||
// This is a duplicate animation creation block, removing it.
|
||||
@@ -1024,8 +1292,6 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
// === FARMING & FOG REMOVED - Ultra Clean ===
|
||||
|
||||
// === PROCEDURAL GROWTH SYSTEM INITIALIZATION ===
|
||||
// TEMPORARILY DISABLED FOR TESTING
|
||||
/*
|
||||
console.log('🌱 Initializing Procedural Growth System...');
|
||||
|
||||
// Initialize soil moisture grid
|
||||
@@ -1048,28 +1314,256 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
});
|
||||
|
||||
console.log('✅ Growth system ready - Island will evolve over time!');
|
||||
*/
|
||||
console.log('🚫 Vegetation DISABLED for testing');
|
||||
|
||||
// === RAIN WEATHER SYSTEM ===
|
||||
// Create rain particles that fall from sky
|
||||
this.rainParticles = this.add.particles(0, -50, 'rain_drops', {
|
||||
x: { min: 0, max: this.scale.width },
|
||||
y: -50,
|
||||
speedY: { min: 300, max: 500 },
|
||||
speedX: { min: -20, max: 20 },
|
||||
lifespan: 3000,
|
||||
scale: { min: 0.3, max: 0.6 },
|
||||
alpha: { start: 0.7, end: 0.3 },
|
||||
frequency: 50,
|
||||
rotate: { min: -10, max: 10 },
|
||||
blendMode: 'ADD',
|
||||
emitting: true
|
||||
// === RAIN WEATHER SYSTEM (Procedural — natural looking) ===
|
||||
// We draw rain as short diagonal lines each frame on a Graphics object.
|
||||
// This avoids the "vertical column" pattern of sprite-based emitters.
|
||||
this.rainGraphics = this.add.graphics()
|
||||
.setScrollFactor(0) // Fixed to screen
|
||||
.setDepth(5000);
|
||||
|
||||
// Rain drop pool — randomised positions, speeds, lengths, and alpha
|
||||
const DROP_COUNT = 160; // Sparse enough to see island behind it
|
||||
const SCREEN_W = this.cameras.main.width;
|
||||
const SCREEN_H = this.cameras.main.height;
|
||||
|
||||
this.rainDrops = [];
|
||||
for (let i = 0; i < DROP_COUNT; i++) {
|
||||
this.rainDrops.push({
|
||||
x: Math.random() * SCREEN_W,
|
||||
y: Math.random() * SCREEN_H,
|
||||
speedY: 420 + Math.random() * 280, // 420–700 px/s — fast
|
||||
speedX: -40 + Math.random() * 20, // Slight left slant
|
||||
length: 8 + Math.random() * 10, // Short: 8–18px
|
||||
alpha: 0.25 + Math.random() * 0.30, // 0.25–0.55 — semi-transparent
|
||||
});
|
||||
}
|
||||
|
||||
// Store screen size for use in update()
|
||||
this.rainScreenW = SCREEN_W;
|
||||
this.rainScreenH = SCREEN_H;
|
||||
|
||||
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!');
|
||||
|
||||
// Spodaj: Fiksno zgenerirana polja za kmetovanje
|
||||
const farmStartX = this.islandX + this.islandWidth / 2 - 80;
|
||||
const farmStartY = this.islandY + this.islandHeight / 2 + 140;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 2; j++) {
|
||||
if (this.farmingSystem._tillGround) {
|
||||
this.farmingSystem._tillGround(farmStartX + i * 48, farmStartY + j * 48);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── WATER SYSTEM ────────────────────────────────────────
|
||||
this.waterSystem = new WaterSystem(this, {
|
||||
tileSize: this.TILE_SIZE,
|
||||
});
|
||||
|
||||
// Zgoraj Desno: Lovilec deževnice (Fiksno za DEMO)
|
||||
const rcX = this.islandX + this.islandWidth / 2 + 128;
|
||||
const rcY = this.islandY + this.islandHeight / 2 - 128;
|
||||
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.2, // 1.2 sec igre na 1 realno sec (20 minut = 1 dan)
|
||||
});
|
||||
|
||||
// ─── ACCESSIBILITY SYSTEM ─────────────────────────────
|
||||
this.accessState = {
|
||||
deafMode: false, autismMode: false, adhdMode: false,
|
||||
fontMode: false, contrastMode: false, colorblindMode: false, onehandMode: false
|
||||
};
|
||||
this.game.events.on('accessibility-changed', (state) => {
|
||||
this.accessState = state;
|
||||
});
|
||||
|
||||
// === 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 dejansko spalno vrečo
|
||||
this.dayNightSystem.registerSleepObject(this.sleepingBag.x, this.sleepingBag.y, this.sleepingBag, '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();
|
||||
|
||||
// ─── TWIN BOND (Telepathy) SYSTEM ─────────────────────────
|
||||
this.twinBondSystem = new TwinBondSystem(this);
|
||||
console.log('✅ TwinBondSystem ready!');
|
||||
|
||||
// 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č!');
|
||||
|
||||
// ─── ODKLEP FAZA 1 (FOG OF WAR LIFT) ──────────────────────
|
||||
this.game.events.on('unlock-faza1', () => {
|
||||
console.log("🔓 FAZA 1 ODKLENJENA! Zmegljen Fog of War, odklenjene zgradbe.");
|
||||
|
||||
// 1. Vizualni indikator umikanja megle
|
||||
const cx = this.cameras.main.centerX;
|
||||
const cy = this.cameras.main.centerY;
|
||||
let fogLiftText = this.add.text(cx, cy - 150, 'NOVA OBMOČJA ODKRITA\n(Megla vojne se dviga)', {
|
||||
fontFamily: 'Arial Black',
|
||||
fontSize: '42px',
|
||||
color: '#44aaff',
|
||||
stroke: '#000',
|
||||
strokeThickness: 8,
|
||||
align: 'center'
|
||||
}).setScrollFactor(0).setOrigin(0.5, 0.5).setDepth(9999).setAlpha(0);
|
||||
|
||||
this.tweens.add({
|
||||
targets: fogLiftText,
|
||||
alpha: 1,
|
||||
y: cy - 200,
|
||||
duration: 2000,
|
||||
ease: 'Sine.easeOut',
|
||||
yoyo: true,
|
||||
hold: 3000,
|
||||
onComplete: () => fogLiftText.destroy()
|
||||
});
|
||||
|
||||
// Umik Fog Of War grafike
|
||||
if (this.fowGraphics) {
|
||||
this.tweens.add({
|
||||
targets: this.fowGraphics,
|
||||
alpha: 0,
|
||||
duration: 3000,
|
||||
ease: 'Sine.easeInOut',
|
||||
onComplete: () => this.fowGraphics.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Dodamo nove zgradbe v BuildingSystem
|
||||
if (this.buildingSystem) {
|
||||
this.buildingSystem.catalogue.push(
|
||||
{
|
||||
key: 'garage_ruin',
|
||||
label: 'Garaža',
|
||||
scale: 4.0,
|
||||
colliderW: 200, colliderH: 80, colliderOffsetX: -100, colliderOffsetY: -40
|
||||
},
|
||||
{
|
||||
key: 'barn_ruin',
|
||||
label: 'Hlev',
|
||||
scale: 4.0,
|
||||
colliderW: 200, colliderH: 80, colliderOffsetX: -100, colliderOffsetY: -40
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// === 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!');
|
||||
}
|
||||
});
|
||||
this.rainParticles.setDepth(5000); // Above everything
|
||||
this.rainParticles.setScrollFactor(0); // Fixed to camera
|
||||
|
||||
console.log('🌧️ Rain system initialized!');
|
||||
}
|
||||
|
||||
|
||||
@@ -1128,11 +1622,49 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
console.log("SFX: THUD!");
|
||||
// this.sound.play('tree_thud');
|
||||
|
||||
// Visual: Screen Shake
|
||||
// Visual: Screen Shake (izklopljen v Autism Mode)
|
||||
if (!this.accessState?.autismMode) {
|
||||
this.cameras.main.shake(300, 0.005);
|
||||
}
|
||||
|
||||
tree.destroy();
|
||||
|
||||
// Odkleni dnevniski vnos ob prvem podrtju drevesa
|
||||
if (this.journalSystem && !this._firstTreeFelled) {
|
||||
this._firstTreeFelled = true;
|
||||
this.time.delayedCall(800, () => {
|
||||
this.journalSystem.unlockEntry('first_tree');
|
||||
});
|
||||
}
|
||||
|
||||
// Drop wood visual (3 pieces)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let wx = tx + (Math.random() - 0.5) * 100;
|
||||
let wy = ty - 50 + (Math.random() - 0.5) * 50;
|
||||
let w = this.add.text(wx, wy, '🪵', { fontSize: '35px' }).setOrigin(0.5);
|
||||
w.setDepth(wy); // y-sorting
|
||||
this.physics.add.existing(w);
|
||||
w.body.setSize(40, 40);
|
||||
|
||||
// Simple jump/pop animation for the drop
|
||||
this.tweens.add({
|
||||
targets: w,
|
||||
y: wy - 40,
|
||||
duration: 200,
|
||||
yoyo: true,
|
||||
ease: 'Quad.easeOut'
|
||||
});
|
||||
|
||||
// Add collision to collect it
|
||||
this.physics.add.overlap(this.kai, w, () => {
|
||||
w.destroy();
|
||||
if (this.inventorySystem) {
|
||||
this.inventorySystem.addItem('wood', 1);
|
||||
this.inventorySystem._showMsg('+1 Les', '#ddaa00');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. AUTO-REPLANT (Spawn Grass Clump)
|
||||
this.spawnReplant(tx, ty); // Use generic regrowth for consistency if desired, or keep as specialized spawn
|
||||
}
|
||||
@@ -1324,14 +1856,16 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
// Check if already placed here
|
||||
if (placed.some(p => p.gridX === gridX && p.gridY === gridY)) continue;
|
||||
|
||||
// World coordinates
|
||||
// World coordinates (tile center)
|
||||
const worldX = this.islandX + (gridX * this.TILE_SIZE) + (this.TILE_SIZE / 2);
|
||||
const worldY = this.islandY + (gridY * this.TILE_SIZE) + (this.TILE_SIZE / 2);
|
||||
const tileBottomY = this.islandY + (gridY * this.TILE_SIZE) + this.TILE_SIZE;
|
||||
|
||||
// Create sapling (small tree)
|
||||
const sapling = this.add.image(worldX, worldY, 'drevo_faza_1')
|
||||
.setScale(0.4) // Small sapling
|
||||
.setDepth(this.LAYER_TREES)
|
||||
const treeScale = this.PIXEL_OAK_HEIGHT / this.SOURCE_TREE_AVG; // Pixel-perfect oak scale
|
||||
const sapling = this.add.image(worldX, tileBottomY + this.ROOT_OFFSET, 'drevo_faza_1')
|
||||
.setScale(treeScale * 0.4) // Small sapling (40% of full oak)
|
||||
.setOrigin(0.5, 1.0) // ANCHOR: Bottom-center
|
||||
.setDepth(tileBottomY + this.ROOT_OFFSET) // DYNAMIC DEPTH: Y-sorting
|
||||
.setAlpha(0.8);
|
||||
|
||||
this.treeSaplings.push({
|
||||
@@ -1374,14 +1908,16 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.treeSaplings.forEach(tree => {
|
||||
tree.age++;
|
||||
|
||||
const treeScale = this.PIXEL_OAK_HEIGHT / this.SOURCE_TREE_AVG;
|
||||
|
||||
// Growth stages: 1→2 at 3 days, 2→3 at 7 days
|
||||
if (tree.age === 3 && tree.stage === 1) {
|
||||
tree.stage = 2;
|
||||
tree.sprite.setTexture('drevo_faza_2').setScale(0.7);
|
||||
tree.sprite.setTexture('drevo_faza_2').setScale(treeScale * 0.7); // 70% of full oak
|
||||
treesGrown++;
|
||||
} else if (tree.age === 7 && tree.stage === 2) {
|
||||
tree.stage = 3;
|
||||
tree.sprite.setTexture('drevo_veliko').setScale(1.0).setAlpha(1.0);
|
||||
tree.sprite.setTexture('drevo_veliko').setScale(treeScale).setAlpha(1.0); // Full 160px oak!
|
||||
treesGrown++;
|
||||
}
|
||||
});
|
||||
@@ -1394,15 +1930,19 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
/** Grow grass at grid position */
|
||||
growGrass(gridX, gridY) {
|
||||
const worldX = this.islandX + (gridX * this.TILE_SIZE) + (this.TILE_SIZE / 2);
|
||||
const worldY = this.islandY + (gridY * this.TILE_SIZE) + (this.TILE_SIZE / 2);
|
||||
const tileBottomY = this.islandY + (gridY * this.TILE_SIZE) + this.TILE_SIZE;
|
||||
|
||||
// Random grass sprite (use available grass assets)
|
||||
const grassTypes = ['grass_ref_1', 'grass_cluster_dense', 'grass_cluster_flowery'];
|
||||
const grassType = Phaser.Utils.Array.GetRandom(grassTypes);
|
||||
|
||||
const grass = this.add.image(worldX, worldY, grassType)
|
||||
.setScale(0.6 + Math.random() * 0.4) // Size variation
|
||||
.setDepth(this.LAYER_VEGETATION) // Layer 1 - below characters!
|
||||
// PIXEL-PERFECT SCALE: Tall grass = 28px height
|
||||
const grassScale = this.PIXEL_GRASS_TALL_AVG / this.SOURCE_GRASS_TALL;
|
||||
|
||||
const grass = this.add.image(worldX, tileBottomY + this.ROOT_OFFSET, grassType)
|
||||
.setScale(grassScale * (0.9 + Math.random() * 0.2)) // Slight variation (90-110% of 28px)
|
||||
.setOrigin(0.5, 1.0) // ANCHOR: Bottom-center
|
||||
.setDepth(tileBottomY + this.ROOT_OFFSET) // DYNAMIC DEPTH: Y-sorting
|
||||
.setAlpha(0);
|
||||
|
||||
// Fade in animation
|
||||
@@ -1439,6 +1979,26 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
/* Removed for cleanup */
|
||||
|
||||
update(time, delta) {
|
||||
if (this.kai && this.kai.active) {
|
||||
// --- LOOKAHEAD CAMERA OFFSET (Gledanje naprej) ---
|
||||
const maxOffset = 180; // Koliko pikslov naprej naj pogleda kamera
|
||||
let targetOffsetX = 0;
|
||||
let targetOffsetY = 0;
|
||||
|
||||
if (this.kai.body.velocity.x > 10) targetOffsetX = maxOffset;
|
||||
else if (this.kai.body.velocity.x < -10) targetOffsetX = -maxOffset;
|
||||
|
||||
if (this.kai.body.velocity.y > 10) targetOffsetY = maxOffset;
|
||||
else if (this.kai.body.velocity.y < -10) targetOffsetY = -maxOffset;
|
||||
|
||||
// Gladko lerpanje proti tarči offseta (manjša vrednost = bolj gladko)
|
||||
this.cameraLookahead.x = Phaser.Math.Linear(this.cameraLookahead.x, targetOffsetX, 0.03);
|
||||
this.cameraLookahead.y = Phaser.Math.Linear(this.cameraLookahead.y, targetOffsetY, 0.03);
|
||||
|
||||
// V Phaserju pozitiven offset zamakne *tarčo*, kar pomeni da gre kamera v drugo smer!
|
||||
this.cameras.main.setFollowOffset(this.cameraLookahead.x, this.cameraLookahead.y);
|
||||
}
|
||||
|
||||
// --- ANIMATE OCEAN ---
|
||||
// (Parallax skipped for rectangles, logic removed to prevent errors)
|
||||
// --- ANIMATE FOAM ---
|
||||
@@ -1486,14 +2046,33 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
});
|
||||
|
||||
// --- PLAYER MOVEMENT (SMOOTHENED) ---
|
||||
if (this.kai.canMove === false) {
|
||||
this.kai.setVelocity(0, 0);
|
||||
this.kai.anims.stop();
|
||||
} else {
|
||||
const speed = 500;
|
||||
const velocity = new Phaser.Math.Vector2(0, 0);
|
||||
|
||||
// Input helpers
|
||||
const left = this.cursors.left.isDown || this.keys.left.isDown;
|
||||
const right = this.cursors.right.isDown || this.keys.right.isDown;
|
||||
const up = this.cursors.up.isDown || this.keys.up.isDown;
|
||||
const down = this.cursors.down.isDown || this.keys.down.isDown;
|
||||
let left = this.cursors.left.isDown || this.keys.left.isDown;
|
||||
let right = this.cursors.right.isDown || this.keys.right.isDown;
|
||||
let up = this.cursors.up.isDown || this.keys.up.isDown;
|
||||
let down = this.cursors.down.isDown || this.keys.down.isDown;
|
||||
|
||||
// ── ENOROČNI NAČIN (Sledenje miški z držanjem klika) ──
|
||||
if (this.accessState?.onehandMode && this.input.activePointer.isDown) {
|
||||
const ptr = this.input.activePointer;
|
||||
// Preprečimo tresenje, če je miška preblizu lika
|
||||
if (Phaser.Math.Distance.Between(this.kai.x, this.kai.y - 30, ptr.worldX, ptr.worldY) > 25) {
|
||||
const angle = Phaser.Math.Angle.Between(this.kai.x, this.kai.y - 30, ptr.worldX, ptr.worldY);
|
||||
const dx = Math.cos(angle);
|
||||
const dy = Math.sin(angle);
|
||||
if (dx < -0.3) left = true;
|
||||
if (dx > 0.3) right = true;
|
||||
if (dy < -0.3) up = true;
|
||||
if (dy > 0.3) down = true;
|
||||
}
|
||||
}
|
||||
const space = this.cursors.space.isDown;
|
||||
|
||||
if (left) velocity.x = -1;
|
||||
@@ -1516,9 +2095,79 @@ export default class GrassSceneClean extends Phaser.Scene {
|
||||
this.kai.setVelocity(0, 0);
|
||||
this.kai.anims.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === ULTRA CLEAN: Only Kai depth sorting ===
|
||||
this.kai.setDepth(this.kai.y);
|
||||
|
||||
// === RAIN UPDATE: Draw procedural raindrops each frame ===
|
||||
if (this.rainGraphics && this.rainDrops) {
|
||||
const dt = delta / 1000; // seconds
|
||||
this.rainGraphics.clear();
|
||||
|
||||
for (const drop of this.rainDrops) {
|
||||
// Move drop
|
||||
drop.x += drop.speedX * dt;
|
||||
drop.y += drop.speedY * dt;
|
||||
|
||||
// Wrap: if off-screen bottom or sides, teleport back to top
|
||||
if (drop.y > this.rainScreenH + 20) {
|
||||
drop.y = -drop.length;
|
||||
drop.x = Math.random() * this.rainScreenW;
|
||||
}
|
||||
if (drop.x < -20) drop.x = this.rainScreenW;
|
||||
if (drop.x > this.rainScreenW + 20) drop.x = 0;
|
||||
|
||||
// Draw as a short diagonal line (not a sprite — no column patterns)
|
||||
this.rainGraphics.lineStyle(1, 0xaaddff, drop.alpha);
|
||||
this.rainGraphics.beginPath();
|
||||
this.rainGraphics.moveTo(drop.x, drop.y);
|
||||
// End point slightly offset in direction of travel
|
||||
const len = drop.length;
|
||||
this.rainGraphics.lineTo(
|
||||
drop.x + drop.speedX * len * 0.012,
|
||||
drop.y + drop.speedY * len * 0.012
|
||||
);
|
||||
this.rainGraphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
// === 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);
|
||||
}
|
||||
|
||||
// === TWIN BOND SYSTEM UPDATE ===
|
||||
if (this.twinBondSystem) {
|
||||
this.twinBondSystem.update(time, delta);
|
||||
}
|
||||
|
||||
// === FARMING SYSTEM UPDATE ===
|
||||
if (this.farmingSystem) {
|
||||
this.farmingSystem.update(this.kai, time);
|
||||
}
|
||||
|
||||
// === BUILDING SYSTEM: Ghost follows pointer, building Y-sorting ===
|
||||
if (this.buildingSystem) {
|
||||
this.buildingSystem.update(this.input.activePointer);
|
||||
}
|
||||
|
||||
// Y-sort placed buildings so Kai can walk behind/in front
|
||||
if (this._buildingSprites) {
|
||||
this._buildingSprites.forEach(b => {
|
||||
if (b && b.active) b.setDepth(b.y);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
377
nova farma TRAE/src/scenes/MenuScene.js
Normal file
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 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
|
||||
* ============================================================
|
||||
*/
|
||||
import LocalizationSystem from '../systems/LocalizationSystem.js';
|
||||
export default class MenuScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'MenuScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.path = 'assets/';
|
||||
|
||||
// Naloži prevode
|
||||
this.load.json('localization', 'localization.json');
|
||||
|
||||
// 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
|
||||
|
||||
// ─── INITIALIZE LOCALIZATION ───
|
||||
this.i18n = new LocalizationSystem(this);
|
||||
this.i18n.init();
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 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: '▶ ' + this.i18n.t('menu_play'), id: 'play', color: '#44ffcc', hColor: '#ffffff' },
|
||||
{ label: '⚙ ' + this.i18n.t('menu_options'), id: 'settings', color: '#aabbcc', hColor: '#ffffff' },
|
||||
{ label: '🌍 ' + this.i18n.t('lang_btn'), id: 'lang', color: '#ccddaa', hColor: '#ffffff' },
|
||||
{ label: '✕ ' + this.i18n.t('menu_exit'), 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: this.i18n.fontFamily,
|
||||
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 === 'lang') {
|
||||
// Cycle language
|
||||
const langs = this.i18n.supportedLangs;
|
||||
let idx = langs.indexOf(this.i18n.currentLang);
|
||||
idx = (idx + 1) % langs.length;
|
||||
this.i18n.setLanguage(langs[idx]);
|
||||
// Restart scene to apply new language
|
||||
this.scene.restart();
|
||||
}
|
||||
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,775 @@
|
||||
/**
|
||||
* =============================================================
|
||||
* 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)
|
||||
* =============================================================
|
||||
*/
|
||||
import LocalizationSystem from '../systems/LocalizationSystem.js';
|
||||
import AccessibilityManager from '../systems/AccessibilityManager.js';
|
||||
|
||||
export default class UIScene extends Phaser.Scene {
|
||||
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,
|
||||
isFullGame: false,
|
||||
accessibility: {
|
||||
deafMode: false, autismMode: false, adhdMode: false,
|
||||
fontMode: false, contrastMode: false, colorblindMode: false, onehandMode: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
preload() {
|
||||
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_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_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_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() {
|
||||
// Simple UI Layout
|
||||
const width = this.cameras.main.width;
|
||||
const height = this.cameras.main.height;
|
||||
const PAD = 60; // Increased padding for better visibility
|
||||
const W = this.cameras.main.width; // 1920
|
||||
const H = this.cameras.main.height; // 1080
|
||||
const PAD = 24;
|
||||
|
||||
// 0. TRIAL BADGE (Top Center - The "Hook")
|
||||
this.add.image(width / 2, PAD, 'ui_badge_trial')
|
||||
.setOrigin(0.5, 0)
|
||||
.setScale(1.0) // Increased scale
|
||||
.setDepth(100); // Ensure it's on top
|
||||
// ─── INITIALIZE LOCALIZATION ───
|
||||
this.i18n = new LocalizationSystem(this);
|
||||
this.i18n.init();
|
||||
|
||||
// 1. Health (Top Left)
|
||||
let health = this.add.image(PAD, PAD, 'ui_health_bar').setOrigin(0, 0).setScale(1.0);
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// TOP LEFT — Trial badge + HP + Energy
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// 2. Weather (Top Right)
|
||||
let weather = this.add.image(width - PAD, PAD, 'ui_weather_widget').setOrigin(1, 0).setScale(1.0);
|
||||
// Trial Version Badge
|
||||
this.trialBadge = this.add.image(PAD, PAD, 'ui_badge_trial')
|
||||
.setOrigin(0, 0)
|
||||
.setScale(0.38)
|
||||
.setDepth(1000);
|
||||
|
||||
// 3. Minimap (Bottom Right)
|
||||
let minimap = this.add.image(width - PAD, height - PAD, 'ui_minimap').setOrigin(1, 1).setScale(0.9);
|
||||
// Badge lebdi animacija
|
||||
this.tweens.add({
|
||||
targets: this.trialBadge,
|
||||
y: PAD + 6,
|
||||
duration: 1800,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut',
|
||||
});
|
||||
|
||||
// 4. Hotbar (Bottom Center)
|
||||
let hotbar = this.add.image(width / 2, height - PAD, 'ui_hotbar').setOrigin(0.5, 1).setScale(1.0);
|
||||
// ── NAKUP IGRE LOGIKA ──
|
||||
this.trialBadge.setInteractive({ useHandCursor: true });
|
||||
this.trialBadge.on('pointerdown', () => {
|
||||
if (!this.gameState.isFullGame) {
|
||||
this.unlockFullGame();
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Action Button (Right of Hotbar)
|
||||
let action = this.add.image(width / 2 + 400, height - PAD, 'ui_action_btn').setOrigin(0.5, 1).setScale(1.0);
|
||||
// ── HP Bar (health_bar.png je vertikalni triptih: full/empty/empty) ─
|
||||
// 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;
|
||||
|
||||
// Debug Text
|
||||
this.add.text(width / 2, 50, 'HUD LAYER ACTIVE', {
|
||||
fontSize: '24px', fill: '#00ff00', stroke: '#000', strokeThickness: 4
|
||||
}).setOrigin(0.5);
|
||||
// Ozadje HP (temno rdeče)
|
||||
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);
|
||||
|
||||
// HP Fill (svetlo rdeča)
|
||||
this.hpFill = this.add.graphics().setDepth(1002);
|
||||
|
||||
// HP ikona (skull) – kratko besedilo label
|
||||
this.add.text(HP_X - 5, HP_Y + HP_H / 2, '❤️', { fontSize: '22px' })
|
||||
.setOrigin(1, 0.5).setDepth(1003);
|
||||
|
||||
// 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();
|
||||
|
||||
// ── ACCESSIBILITY MENU BUTTON ──
|
||||
this.accBtn = this.add.text(W - PAD, PAD + 180, '⚙️ ' + this.i18n.t('acc_menu'), {
|
||||
fontFamily: this.i18n.fontFamily,
|
||||
fontSize: '18px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#000000aa',
|
||||
padding: { x: 10, y: 5 },
|
||||
stroke: '#ff00ff',
|
||||
strokeThickness: 2
|
||||
}).setOrigin(1, 0).setInteractive({ useHandCursor: true }).setDepth(1000);
|
||||
|
||||
// ── INIT ACCESSIBILITY MANAGER ──
|
||||
this.accManager = new AccessibilityManager(this);
|
||||
this.accManager.setupMenu(W, H);
|
||||
|
||||
this.accBtn.on('pointerdown', () => this.accManager.toggleMenu());
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 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, this.i18n.t('day') + ' 1', {
|
||||
fontFamily: this.i18n.fontFamily,
|
||||
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, this.i18n.t('crafting'), {
|
||||
fontFamily: this.i18n.fontFamily,
|
||||
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, this.i18n.t('map'), {
|
||||
fontFamily: this.i18n.fontFamily,
|
||||
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);
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// FULL GAME UNLOCK
|
||||
// =========================================================
|
||||
unlockFullGame() {
|
||||
this.gameState.isFullGame = true;
|
||||
|
||||
// Zamenjaj badge
|
||||
this.trialBadge.setTexture('ui_badge_buy');
|
||||
this.trialBadge.clearTint();
|
||||
|
||||
// Dark-Chibi Noir UI stil: Velik napis preko ekrana
|
||||
const W = this.cameras.main.width;
|
||||
const H = this.cameras.main.height;
|
||||
let unlockText = this.add.text(W / 2, H / 2, 'ODKLENJENO\nFULL GAME', {
|
||||
fontFamily: 'Arial Black',
|
||||
fontSize: '72px',
|
||||
color: '#44ffaa',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 10,
|
||||
align: 'center'
|
||||
}).setOrigin(0.5, 0.5).setDepth(9999).setAlpha(0);
|
||||
|
||||
// Zatemnitev ozadja
|
||||
let dim = this.add.rectangle(W/2, H/2, W, H, 0x000000, 0.6)
|
||||
.setDepth(9998).setAlpha(0);
|
||||
|
||||
this.tweens.add({
|
||||
targets: [unlockText, dim],
|
||||
alpha: 1,
|
||||
duration: 1500,
|
||||
ease: 'Power2',
|
||||
yoyo: true,
|
||||
hold: 3000,
|
||||
onComplete: () => {
|
||||
unlockText.destroy();
|
||||
dim.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Pošlji signal v GrassScene, da se odklene Faza 1
|
||||
this.game.events.emit('unlock-faza1');
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// ADHD MODE TOGGLE
|
||||
// =========================================================
|
||||
toggleADHDMode(isOn) {
|
||||
let alpha = isOn ? 0.0 : 1.0; // Popolnoma skrij ali prikaži
|
||||
|
||||
// Vse elemente, ki niso nujni, skrijemo
|
||||
const hideEls = [
|
||||
this.weatherWidget, this.timeText, this.dayText,
|
||||
this.trialBadge, this.hpBg, this.hpFill, this.hpText,
|
||||
this.energyBg, this.energyFill, this.energyText,
|
||||
this.hotbarBg, this.actionBtn,
|
||||
this.minimapFrame, this.minimapBg, this.minimapKaiDot
|
||||
// this.accBtn namerno ni tukaj, da ga igralec še vedno lahko klikne in ugasne mode!
|
||||
];
|
||||
|
||||
for (const el of hideEls) {
|
||||
if (el) el.setAlpha(alpha);
|
||||
}
|
||||
|
||||
// Skrij tudi hotbar slot elemente
|
||||
if (this.hotbarSlots) {
|
||||
for (const slot of this.hotbarSlots) {
|
||||
if (slot.numLabel) slot.numLabel.setAlpha(alpha);
|
||||
if (slot.highlight) slot.highlight.setAlpha(alpha);
|
||||
if (slot.itemSprite) slot.itemSprite.setAlpha(alpha);
|
||||
if (slot.qtyText) slot.qtyText.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
||||
// Prikaži velik utripajoč cilj
|
||||
if (isOn && !this.adhdGoal) {
|
||||
const W = this.cameras.main.width;
|
||||
this.adhdGoal = this.add.text(W / 2, 100, this.i18n.t('adhd_goal'), {
|
||||
fontFamily: this.i18n.fontFamily, fontSize: '32px', color: '#ffff00', backgroundColor: '#000000aa', padding: { x: 20, y: 10 }, stroke: '#000', strokeThickness: 6
|
||||
}).setOrigin(0.5).setDepth(9998);
|
||||
this.tweens.add({ targets: this.adhdGoal, alpha: 0.3, duration: 800, yoyo: true, repeat: -1 });
|
||||
} else if (!isOn && this.adhdGoal) {
|
||||
this.adhdGoal.destroy();
|
||||
this.adhdGoal = null;
|
||||
}
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
if (this.accManager) {
|
||||
this.accManager.update(time, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
190
nova farma TRAE/src/systems/AccessibilityManager.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* AccessibilityManager.js
|
||||
*
|
||||
* Vodi celoten Accessibility Meni ter vizualna opozorila za Deaf Mode.
|
||||
*/
|
||||
export default class AccessibilityManager {
|
||||
constructor(scene) {
|
||||
this.scene = scene; // UIScene
|
||||
this.deafAlertRed = null;
|
||||
this.twinBondGlow = null;
|
||||
this.glitchOverlay = null;
|
||||
|
||||
// Poslušaj evente
|
||||
if (this.scene.game) {
|
||||
this.scene.game.events.on('twinBondPulse', this.triggerTwinBondGlow, this);
|
||||
this.scene.game.events.on('flashbackPulse', this.triggerFlashbackGlitch, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Nastavi celoten meni (klicano iz UIScene)
|
||||
setupMenu(W, H) {
|
||||
this.accMenuContainer = this.scene.add.container(W / 2, H / 2).setDepth(9999).setVisible(false);
|
||||
|
||||
let bg = this.scene.add.rectangle(0, 0, 800, 500, 0x111111, 0.95).setStrokeStyle(4, 0xff00ff);
|
||||
let title = this.scene.add.text(0, -210, this.scene.i18n.t('acc_menu'), {
|
||||
fontFamily: this.scene.i18n.fontFamily, fontSize: '24px', color: '#ffdd00'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// STOLPEC 1
|
||||
let btn1 = this.createAccButton(-190, -110, this.scene.i18n.t('acc_deaf'), 'deafMode');
|
||||
let btn2 = this.createAccButton(-190, -40, this.scene.i18n.t('acc_autism'), 'autismMode');
|
||||
let btn3 = this.createAccButton(-190, 30, this.scene.i18n.t('acc_adhd'), 'adhdMode');
|
||||
let btn4 = this.createAccButton(-190, 100, this.scene.i18n.t('acc_onehand'), 'onehandMode');
|
||||
|
||||
// STOLPEC 2
|
||||
let btn5 = this.createAccButton(190, -110, this.scene.i18n.t('acc_font'), 'fontMode');
|
||||
let btn6 = this.createAccButton(190, -40, this.scene.i18n.t('acc_contrast'), 'contrastMode');
|
||||
let btn7 = this.createAccButton(190, 30, this.scene.i18n.t('acc_colorblind'), 'colorblindMode');
|
||||
|
||||
let closeBtn = this.scene.add.text(0, 200, this.scene.i18n.t('acc_close'), { fontFamily: this.scene.i18n.fontFamily, fontSize: '20px', color: '#ff4444' })
|
||||
.setOrigin(0.5).setInteractive({ useHandCursor: true });
|
||||
closeBtn.on('pointerdown', () => this.toggleMenu());
|
||||
|
||||
this.accMenuContainer.add([bg, title, btn1, btn2, btn3, btn4, btn5, btn6, btn7, closeBtn]);
|
||||
}
|
||||
|
||||
createAccButton(x, y, text, key) {
|
||||
let container = this.scene.add.container(x, y);
|
||||
let bg = this.scene.add.rectangle(0, 0, 360, 50, 0x222222).setInteractive({ useHandCursor: true });
|
||||
let txt = this.scene.add.text(0, 0, text + ' [OFF]', { fontFamily: this.scene.i18n.fontFamily, fontSize: '15px', color: '#aaaaaa' }).setOrigin(0.5);
|
||||
|
||||
bg.on('pointerdown', () => {
|
||||
this.scene.gameState.accessibility[key] = !this.scene.gameState.accessibility[key];
|
||||
let isOn = this.scene.gameState.accessibility[key];
|
||||
bg.fillColor = isOn ? 0x004400 : 0x222222;
|
||||
txt.setColor(isOn ? '#44ff44' : '#aaaaaa');
|
||||
txt.setText(text + (isOn ? ' [ON]' : ' [OFF]'));
|
||||
|
||||
this.scene.game.events.emit('accessibility-changed', this.scene.gameState.accessibility);
|
||||
|
||||
// Apply specific immediate effects
|
||||
if (key === 'adhdMode') this.scene.toggleADHDMode(isOn);
|
||||
if (key === 'contrastMode' || key === 'colorblindMode') this.updateCanvasFilters();
|
||||
if (key === 'fontMode') this.toggleLargeFont(isOn);
|
||||
});
|
||||
|
||||
container.add([bg, txt]);
|
||||
return container;
|
||||
}
|
||||
|
||||
updateCanvasFilters() {
|
||||
const isContrast = this.scene.gameState.accessibility.contrastMode;
|
||||
const isColorblind = this.scene.gameState.accessibility.colorblindMode;
|
||||
|
||||
let filters = [];
|
||||
if (isContrast) filters.push('contrast(1.5) drop-shadow(0 0 10px rgba(0,0,0,0.8))');
|
||||
if (isColorblind) filters.push('saturate(1.5) sepia(0.3) hue-rotate(15deg)'); // Simple protanopia filter tweak
|
||||
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.style.filter = filters.length > 0 ? filters.join(' ') : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
toggleLargeFont(isOn) {
|
||||
// Enostavna povečava vseh tekstov v UI (če je true, x1.2)
|
||||
const scale = isOn ? 1.25 : 1.0;
|
||||
this.scene.children.list.forEach(child => {
|
||||
if (child.type === 'Text' && child !== this.accMenuContainer) {
|
||||
this.scene.tweens.add({ targets: child, scaleX: scale, scaleY: scale, duration: 200 });
|
||||
}
|
||||
});
|
||||
// Tudi v GrassScene
|
||||
const grass = this.scene.scene.get('GrassScene');
|
||||
if (grass) {
|
||||
grass.children.list.forEach(child => {
|
||||
if (child.type === 'Text') {
|
||||
grass.tweens.add({ targets: child, scaleX: scale, scaleY: scale, duration: 200 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
if (this.accMenuContainer) {
|
||||
this.accMenuContainer.setVisible(!this.accMenuContainer.visible);
|
||||
}
|
||||
}
|
||||
|
||||
// Klicano v UIScene update
|
||||
update(time, delta) {
|
||||
const deafModeActive = this.scene.gameState.accessibility.deafMode;
|
||||
|
||||
if (!deafModeActive) {
|
||||
if (this.deafAlertRed) { this.deafAlertRed.destroy(); this.deafAlertRed = null; }
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Nevarnost zombijev
|
||||
const grass = this.scene.scene.get('GrassScene');
|
||||
if (grass && grass.zombieSystem && grass.kai) {
|
||||
const zombies = grass.zombieSystem.getZombies();
|
||||
let near = false;
|
||||
for (const z of zombies) {
|
||||
if (!z.tamed && Phaser.Math.Distance.Between(z.x, z.y, grass.kai.x, grass.kai.y) < 550) {
|
||||
near = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (near) {
|
||||
if (!this.deafAlertRed) {
|
||||
const W = this.scene.cameras.main.width;
|
||||
const H = this.scene.cameras.main.height;
|
||||
this.deafAlertRed = this.scene.add.rectangle(W/2, H/2, W, H).setStrokeStyle(30, 0xff0000).setDepth(9999);
|
||||
this.deafAlertRed.isFilled = false;
|
||||
this.scene.tweens.add({ targets: this.deafAlertRed, alpha: 0.2, duration: 400, yoyo: true, repeat: -1 });
|
||||
}
|
||||
} else {
|
||||
if (this.deafAlertRed) { this.deafAlertRed.destroy(); this.deafAlertRed = null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Twin Bond Glow
|
||||
triggerTwinBondGlow() {
|
||||
if (!this.scene.gameState.accessibility.deafMode) return;
|
||||
if (this.twinBondGlow) { this.twinBondGlow.destroy(); }
|
||||
|
||||
const W = this.scene.cameras.main.width;
|
||||
const H = this.scene.cameras.main.height;
|
||||
this.twinBondGlow = this.scene.add.rectangle(W/2, H/2, W, H, 0xaa00ff, 0.4)
|
||||
.setDepth(9998)
|
||||
.setBlendMode(Phaser.BlendModes.SCREEN);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this.twinBondGlow,
|
||||
alpha: 0,
|
||||
duration: 2000,
|
||||
ease: 'Sine.inOut',
|
||||
onComplete: () => {
|
||||
if (this.twinBondGlow) { this.twinBondGlow.destroy(); this.twinBondGlow = null; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Amnezija Spomini (Flashback glitch)
|
||||
triggerFlashbackGlitch() {
|
||||
if (!this.scene.gameState.accessibility.deafMode) return;
|
||||
if (this.glitchOverlay) { this.glitchOverlay.destroy(); }
|
||||
|
||||
const W = this.scene.cameras.main.width;
|
||||
const H = this.scene.cameras.main.height;
|
||||
|
||||
// Screen pulse / flash za flashback
|
||||
this.glitchOverlay = this.scene.add.rectangle(W/2, H/2, W, H, 0xffffff, 0.9).setDepth(10000);
|
||||
|
||||
// Močan tresljaj kamere
|
||||
this.scene.cameras.main.shake(300, 0.03);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this.glitchOverlay,
|
||||
alpha: 0,
|
||||
duration: 400,
|
||||
ease: 'Bounce.out',
|
||||
onComplete: () => {
|
||||
if (this.glitchOverlay) { this.glitchOverlay.destroy(); this.glitchOverlay = null; }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
275
nova farma TRAE/src/systems/BuildingSystem.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* BUILDING SYSTEM — Nova Farma
|
||||
* ============================================================
|
||||
* [B] = vklopi / izklopi build mode
|
||||
* [Esc] = izhod
|
||||
* Klik = postavi stavbo
|
||||
*
|
||||
* UI meni je ODSTRANJEN — stavbe se dodajajo programsko
|
||||
* prek: buildingSystem.selectBuilding('sotor')
|
||||
* ============================================================
|
||||
*/
|
||||
export default class BuildingSystem {
|
||||
|
||||
/**
|
||||
* @param {Phaser.Scene} scene
|
||||
* @param {Phaser.Physics.Arcade.Sprite} kai
|
||||
*/
|
||||
constructor(scene, kai) {
|
||||
this.scene = scene;
|
||||
this.kai = kai;
|
||||
|
||||
this.active = false;
|
||||
this.selectedKey = null;
|
||||
this.ghost = null;
|
||||
this.placed = [];
|
||||
|
||||
// Static physics group za vse postavljene stavbe
|
||||
this.buildingsGroup = scene.physics.add.staticGroup();
|
||||
|
||||
// Katalog stavb — tukaj dodajamo nove
|
||||
this.catalogue = [
|
||||
{
|
||||
key: 'wooden_fence',
|
||||
label: 'Lesena Barikada',
|
||||
scale: 0.5,
|
||||
colliderW: 80,
|
||||
colliderH: 20,
|
||||
colliderOffsetX: -5,
|
||||
colliderOffsetY: 0,
|
||||
cost: { item: 'wood', amount: 2 }
|
||||
},
|
||||
{
|
||||
key: 'sotor',
|
||||
label: 'Šotor',
|
||||
scale: 2.5, // 2.5× Kai (64px) = 160px
|
||||
colliderW: 160,
|
||||
colliderH: 40,
|
||||
colliderOffsetX: -80,
|
||||
colliderOffsetY: -40,
|
||||
},
|
||||
{
|
||||
key: 'campfire',
|
||||
label: 'Taborni ogenj',
|
||||
scale: 1.0,
|
||||
colliderW: 50,
|
||||
colliderH: 20,
|
||||
colliderOffsetX: -25,
|
||||
colliderOffsetY: -20,
|
||||
},
|
||||
];
|
||||
|
||||
this._setupInput();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// KEYBOARD: [B] toggle, [Esc] izhod
|
||||
// ==========================================
|
||||
_setupInput() {
|
||||
this.scene.input.keyboard.on('keydown-B', () => {
|
||||
this.active ? this.deactivate() : this.activate();
|
||||
});
|
||||
|
||||
this.scene.input.keyboard.on('keydown-ESC', () => {
|
||||
if (this.active) this.deactivate();
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ACTIVATE
|
||||
// ==========================================
|
||||
activate() {
|
||||
this.active = true;
|
||||
|
||||
if (!this.selectedKey) {
|
||||
this.selectBuilding(this.catalogue[0].key);
|
||||
} else {
|
||||
this._createGhost(this.selectedKey);
|
||||
}
|
||||
|
||||
if (!this._modeTip) {
|
||||
this._modeTip = this.scene.add.text(
|
||||
this.scene.cameras.main.width / 2, 20,
|
||||
'🏗️ NAČIN GRADNJE | 🖱️ postavi | B / Esc izhod | R rotacija',
|
||||
{
|
||||
fontSize: '16px',
|
||||
color: '#44ffaa',
|
||||
stroke: '#000',
|
||||
strokeThickness: 3,
|
||||
fontFamily: 'Arial',
|
||||
backgroundColor: '#0a0a0f99',
|
||||
padding: { x: 10, y: 4 },
|
||||
}
|
||||
).setOrigin(0.5, 0).setScrollFactor(0).setDepth(20010);
|
||||
}
|
||||
this._modeTip.setVisible(true);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DEACTIVATE
|
||||
// ==========================================
|
||||
deactivate() {
|
||||
this.active = false;
|
||||
this.selectedKey = null;
|
||||
|
||||
if (this.ghost) { this.ghost.destroy(); this.ghost = null; }
|
||||
if (this._modeTip) this._modeTip.setVisible(false);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SELECT BUILDING TYPE
|
||||
// ==========================================
|
||||
selectBuilding(key) {
|
||||
this.selectedKey = key;
|
||||
if (!this.active) this.activate();
|
||||
|
||||
if (this.ghost) { this.ghost.destroy(); this.ghost = null; }
|
||||
this._createGhost(key);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// GHOST PREVIEW
|
||||
// ==========================================
|
||||
_createGhost(key) {
|
||||
const def = this.catalogue.find(c => c.key === key);
|
||||
if (!def) return;
|
||||
|
||||
const KAI_H = 64;
|
||||
const targetH = KAI_H * def.scale;
|
||||
const srcH = this.scene.textures.get(key).getSourceImage().height;
|
||||
const scale = targetH / srcH;
|
||||
|
||||
this.ghost = this.scene.add.image(0, 0, key)
|
||||
.setScale(scale)
|
||||
.setOrigin(0.5, 1.0)
|
||||
.setAlpha(0.6)
|
||||
.setTint(0x44ffaa)
|
||||
.setDepth(99999);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// UPDATE — klic iz scene.update()
|
||||
// ==========================================
|
||||
update(pointer) {
|
||||
if (!this.active || !this.ghost) return;
|
||||
|
||||
const wx = pointer.worldX;
|
||||
const wy = pointer.worldY;
|
||||
|
||||
// Snapping na grid 32px
|
||||
const grid = 32;
|
||||
const finalX = Math.round(wx / grid) * grid;
|
||||
const finalY = Math.round(wy / grid) * grid;
|
||||
|
||||
this.ghost.setPosition(finalX, finalY);
|
||||
|
||||
// Preveri surovine in lokacijo
|
||||
const def = this.catalogue.find(c => c.key === this.selectedKey);
|
||||
let ok = true;
|
||||
|
||||
const b = this.scene.physics.world.bounds;
|
||||
if (finalX <= b.x || finalX >= b.x + b.width || finalY <= b.y || finalY >= b.y + b.height) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (def && def.cost && this.scene.inventorySystem) {
|
||||
const item = this.scene.inventorySystem.items[def.cost.item];
|
||||
if (!item || item.count < def.cost.amount) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.ghost.setTint(ok ? 0x44ffaa : 0xff4444);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PLACE BUILDING (ob kliku)
|
||||
// ==========================================
|
||||
placeBuilding(worldX, worldY) {
|
||||
if (!this.active || !this.selectedKey || !this.ghost) return;
|
||||
|
||||
const def = this.catalogue.find(c => c.key === this.selectedKey);
|
||||
if (!def) return;
|
||||
|
||||
const finalX = this.ghost.x;
|
||||
const finalY = this.ghost.y;
|
||||
|
||||
// Validacija — mora biti na otoku
|
||||
const b = this.scene.physics.world.bounds;
|
||||
if (finalX <= b.x || finalX >= b.x + b.width || finalY <= b.y || finalY >= b.y + b.height) {
|
||||
this._flashGhost();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validacija surovin
|
||||
if (def.cost && this.scene.inventorySystem) {
|
||||
const item = this.scene.inventorySystem.items[def.cost.item];
|
||||
if (!this.scene.inventorySystem.deductItem(def.cost.item, def.cost.amount)) {
|
||||
this._flashGhost();
|
||||
this.scene.inventorySystem._showMsg(`Premalo ${def.cost.item}!`, '#ff4444');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Izračun scale
|
||||
const KAI_H = 64;
|
||||
const targetH = KAI_H * def.scale;
|
||||
const texture = this.scene.textures.get(this.selectedKey);
|
||||
const srcH = texture.getSourceImage().height;
|
||||
const scale = targetH / srcH;
|
||||
|
||||
// Ustvari static physics image
|
||||
const building = this.scene.physics.add.staticImage(finalX, finalY, this.selectedKey)
|
||||
.setScale(scale)
|
||||
.setOrigin(0.5, 1.0)
|
||||
.setDepth(finalY);
|
||||
|
||||
// Physics collider body (samo baza stavbe)
|
||||
building.body.setSize(def.colliderW, def.colliderH);
|
||||
building.body.setOffset(
|
||||
def.colliderOffsetX || 0,
|
||||
def.colliderOffsetY || 0
|
||||
);
|
||||
building.body.reset(finalX, finalY);
|
||||
|
||||
// Collider med Kai in stavbo
|
||||
this.buildingsGroup.add(building, true);
|
||||
this.scene.physics.add.collider(this.kai, building);
|
||||
|
||||
// Spawn animacija
|
||||
building.setAlpha(0);
|
||||
this.scene.tweens.add({
|
||||
targets: building,
|
||||
alpha: 1,
|
||||
duration: 280,
|
||||
ease: 'Back.out',
|
||||
});
|
||||
|
||||
// Flash feedback
|
||||
const flash = this.scene.add.rectangle(finalX, finalY - targetH * 0.5, 80, 80, 0x44ffaa, 0.6)
|
||||
.setDepth(99998);
|
||||
this.scene.tweens.add({
|
||||
targets: flash,
|
||||
alpha: 0, scaleX: 2, scaleY: 2,
|
||||
duration: 350,
|
||||
onComplete: () => flash.destroy(),
|
||||
});
|
||||
|
||||
this.placed.push({ key: this.selectedKey, x: finalX, y: finalY });
|
||||
|
||||
console.log(`🏗️ Placed: ${this.selectedKey} @ (${Math.round(finalX)}, ${Math.round(finalY)})`);
|
||||
}
|
||||
|
||||
_flashGhost() {
|
||||
if (this.ghost) {
|
||||
this.scene.tweens.add({
|
||||
targets: this.ghost,
|
||||
alpha: { from: 0.9, to: 0.2 },
|
||||
duration: 80,
|
||||
yoyo: true,
|
||||
repeat: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
503
nova farma TRAE/src/systems/DayNightSystem.js
Normal file
@@ -0,0 +1,503 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 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.2 igra minut = 1 real sekunda (24 ur v igri = 1200 real sekund = 20 minut)
|
||||
this.gameMinutesPerRealSecond = options.speed || 1.2;
|
||||
|
||||
// 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č) - Pokriva celoten svet!
|
||||
this.ambientOverlay = scene.add.graphics()
|
||||
.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();
|
||||
}
|
||||
|
||||
// Kazen za nespametne: Omedli ob 02:00, če še ne spi
|
||||
if (this.hour === 2 && this.minute === 0 && !this.isSleeping) {
|
||||
this.passOut();
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// PASS OUT — Omedli ob 02:00 (Kazen)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
passOut() {
|
||||
if (this.isSleeping) return;
|
||||
this.isSleeping = true;
|
||||
console.log(`💀 KAI JE OMEDLEL OD UTRUJENOSTI (02:00)!`);
|
||||
|
||||
// Zatemnitev zaslona in rdeč napis
|
||||
this.scene.cameras.main.fadeOut(1500, 20, 0, 0);
|
||||
|
||||
const cx = this.scene.cameras.main.centerX;
|
||||
const cy = this.scene.cameras.main.centerY;
|
||||
|
||||
let passOutText = this.scene.add.text(cx, cy, 'OMEDLEL SI\n(Kazen: -Energija, -Denar)', {
|
||||
fontFamily: 'Arial Black',
|
||||
fontSize: '48px',
|
||||
color: '#ff2222',
|
||||
stroke: '#000000',
|
||||
strokeThickness: 8,
|
||||
align: 'center'
|
||||
}).setOrigin(0.5, 0.5).setDepth(9999).setScrollFactor(0);
|
||||
|
||||
this.scene.time.delayedCall(2000, () => {
|
||||
// Skok na jutro
|
||||
this.hour = 6;
|
||||
this.minute = 0;
|
||||
|
||||
// Kazni
|
||||
if (this.scene.gameState) {
|
||||
this.scene.gameState.energy = Math.max(10, this.scene.gameState.energy - 50); // Zelo malo energije ostane
|
||||
this.scene.events.emit('ui-update-energy', this.scene.gameState.energy);
|
||||
}
|
||||
if (this.scene.playerGold !== undefined) {
|
||||
this.scene.playerGold = Math.max(0, this.scene.playerGold - 50); // Izgubi 50g
|
||||
const uiScene = this.scene.scene.get('UIScene');
|
||||
if (uiScene && uiScene.events) uiScene.events.emit('goldChanged', this.scene.playerGold);
|
||||
}
|
||||
|
||||
// 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'
|
||||
});
|
||||
|
||||
// Odstrani tekst in fade in
|
||||
passOutText.destroy();
|
||||
this.scene.cameras.main.fadeIn(1500, 0, 0, 0);
|
||||
|
||||
this._showDayOverlay(this.day);
|
||||
|
||||
this.scene.time.delayedCall(1600, () => {
|
||||
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 (Dark-Chibi Noir Stil)
|
||||
alpha = 0.85; // Zelo mračno, nevarno
|
||||
r = 0x01; g = 0x01; b = 0x08; // Črna / globoko temno modra
|
||||
} else if (h >= 4 && h < 6) {
|
||||
// Pred zoro
|
||||
const t = (h - 4) / 2;
|
||||
alpha = 0.85 * (1 - t * 0.5);
|
||||
r = 0x01; g = 0x01; b = 0x08;
|
||||
}
|
||||
|
||||
this.ambientOverlay.clear();
|
||||
if (alpha > 0.01) {
|
||||
this.ambientOverlay.fillStyle(
|
||||
(r << 16) | (g << 8) | b,
|
||||
alpha
|
||||
);
|
||||
// Ker imava izklopljen GPU, .setScrollFactor(0) včasih povzroča
|
||||
// težave pri zoomu. Zato je NAJSIGURNEJE preprosto narisati čez celoten 'svet' (10000x10000)
|
||||
// Tako bo "celoten otok" vedno pokrit, neglede na kamero in pozicijo!
|
||||
this.ambientOverlay.fillRect(-2000, -2000, 15000, 15000);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// ZVEZDE (Noč)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
_initStars() {
|
||||
const W = this.scene.cameras.main.width;
|
||||
this.starGfx = this.scene.add.graphics()
|
||||
.setDepth(4499);
|
||||
|
||||
// Zvezde raztrosimo čez celoten svet (zgoraj)
|
||||
for (let i = 0; i < 400; i++) {
|
||||
this.stars.push({
|
||||
x: -2000 + Math.random() * 12000,
|
||||
y: -2000 + Math.random() * 8000,
|
||||
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();
|
||||
}
|
||||
}
|
||||
674
nova farma TRAE/src/systems/FarmingSystem.js
Normal file
@@ -0,0 +1,674 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 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}`);
|
||||
|
||||
// Odkleni dnevniški vnos ob prvem sajenju
|
||||
if (!this._firstPlant && this.scene.journalSystem) {
|
||||
this._firstPlant = true;
|
||||
this.scene.journalSystem.unlockEntry('first_plant');
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// 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`);
|
||||
|
||||
// Odkleni dnevniški vnos ob prvi žetvi
|
||||
if (!this._firstHarvest && this.scene.journalSystem) {
|
||||
this._firstHarvest = true;
|
||||
this.scene.time.delayedCall(500, () => {
|
||||
this.scene.journalSystem.unlockEntry('first_harvest');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// 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 = [];
|
||||
}
|
||||
}
|
||||
361
nova farma TRAE/src/systems/InventorySystem.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 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 (začenjamo prazni!)
|
||||
this.hotbar = [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
];
|
||||
this.activeSlot = 0; // Slot 1
|
||||
|
||||
// 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',
|
||||
},
|
||||
axe: {
|
||||
name: 'Sekira',
|
||||
icon: '🪓',
|
||||
type: 'tool',
|
||||
tool: 'axe',
|
||||
stackable: false,
|
||||
count: 1,
|
||||
desc: '[M1] Podri drevo',
|
||||
},
|
||||
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: '[E] Posadi na preorano',
|
||||
color: '#88cc44',
|
||||
value: 50,
|
||||
},
|
||||
// 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,
|
||||
},
|
||||
wood: {
|
||||
name: 'Les',
|
||||
icon: '🪵',
|
||||
type: 'resource',
|
||||
stackable: true,
|
||||
count: 0,
|
||||
value: 10,
|
||||
},
|
||||
};
|
||||
|
||||
// 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 in item še ni v hotbaru
|
||||
const def = this.ITEM_DEFS[key];
|
||||
if (def && !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)`);
|
||||
}
|
||||
|
||||
deductItem(key, amount) {
|
||||
if (!this.items[key] || this.items[key].count < amount) return false;
|
||||
|
||||
this.items[key].count -= amount;
|
||||
|
||||
// Če je stackable in gre na 0, odstrani tudi iz hotbara
|
||||
if (this.items[key].count <= 0 && this.items[key].stackable) {
|
||||
const index = this.hotbar.indexOf(key);
|
||||
if (index !== -1) {
|
||||
this.hotbar[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._notifyUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
334
nova farma TRAE/src/systems/JournalSystem.js
Normal file
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* JournalSystem.js — Kaijev Dnevnik (Nova Farma)
|
||||
* ============================================================
|
||||
* [J] = odpri / zapri dnevnik
|
||||
* Entries se odklenejo ob napredku (ob klicu unlockEntry(id))
|
||||
* Stil: Temni gotski dnevnik z rumenimi robovi
|
||||
* ============================================================
|
||||
*/
|
||||
export default class JournalSystem {
|
||||
|
||||
/**
|
||||
* @param {Phaser.Scene} scene - GrassSceneClean instanca
|
||||
*/
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.isOpen = false;
|
||||
this.container = null;
|
||||
this.currentPage = 0;
|
||||
|
||||
// ── Vse Vnosi (se odklenejo z unlockEntry) ──────────────────────
|
||||
this.entries = [
|
||||
{
|
||||
id: 'start',
|
||||
title: 'Dan 1 — Začetek',
|
||||
text: [
|
||||
'Prebudil sem se na otoku.',
|
||||
'Nič se ne spomnim. Samo glas v glavi:',
|
||||
'"Preživi. Posadi. Gradi."',
|
||||
'',
|
||||
'Poleg mene je bila skrinja. V njej orodja.',
|
||||
'Začenjam.',
|
||||
],
|
||||
icon: '📖',
|
||||
unlocked: true, // Startni vnos je vedno aktiven
|
||||
},
|
||||
{
|
||||
id: 'first_tree',
|
||||
title: 'Les z Gozda',
|
||||
text: [
|
||||
'Sekira se je dobro obnesla.',
|
||||
'Drevo je padlo, les se je zvril na tla.',
|
||||
'S tem lesom bom zgradil prve barikade.',
|
||||
'',
|
||||
'Noč se bliža. Slutim, da bo grdo.',
|
||||
],
|
||||
icon: '🪓',
|
||||
unlocked: false,
|
||||
},
|
||||
{
|
||||
id: 'first_plant',
|
||||
title: 'Prve Sadike',
|
||||
text: [
|
||||
'Zemlja je rjava in mokra.',
|
||||
'Prekopal sem jo z motiko, posadil pšenico.',
|
||||
'Vsako jutro jo moram zaliti.',
|
||||
'',
|
||||
'Upam, da bo rasla hitro. Hrane je vedno manj.',
|
||||
],
|
||||
icon: '🌱',
|
||||
unlocked: false,
|
||||
},
|
||||
{
|
||||
id: 'first_night',
|
||||
title: 'Prva Noč',
|
||||
text: [
|
||||
'Slišim jih.',
|
||||
'Ropot v temi, stokanje. Hodijo počasi.',
|
||||
'',
|
||||
'Barikade so zdržale to noč.',
|
||||
'A koliko noči jih je še pred mano?',
|
||||
],
|
||||
icon: '🌙',
|
||||
unlocked: false,
|
||||
},
|
||||
{
|
||||
id: 'first_harvest',
|
||||
title: 'Žetev',
|
||||
text: [
|
||||
'Pšenica je zrasla!',
|
||||
'Tri klasje. Malo, a dovolj za začetek.',
|
||||
'',
|
||||
'Zemlja tukaj je rodovitna kljub vsemu.',
|
||||
'Morda je to kraj, kjer se da preživeti.',
|
||||
],
|
||||
icon: '🌾',
|
||||
unlocked: false,
|
||||
},
|
||||
{
|
||||
id: 'mystery',
|
||||
title: '???',
|
||||
text: [
|
||||
'???',
|
||||
],
|
||||
icon: '❓',
|
||||
unlocked: false,
|
||||
},
|
||||
];
|
||||
|
||||
// ── UI Setup ──────────────────────────────────────────────────────
|
||||
this._buildUI();
|
||||
|
||||
// ── Tipka [J] ─────────────────────────────────────────────────────
|
||||
this.keyJ = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.J);
|
||||
this.keyJ.on('down', this.toggle, this);
|
||||
|
||||
// ── Zapri z [Esc] ─────────────────────────────────────────────────
|
||||
scene.input.keyboard.on('keydown-ESC', () => {
|
||||
if (this.isOpen) this.close();
|
||||
});
|
||||
|
||||
console.log('📖 JournalSystem initialized — [J] za dnevnik');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// BUILD UI
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
_buildUI() {
|
||||
const { width, height } = this.scene.cameras.main;
|
||||
const W = Math.min(660, width - 60);
|
||||
const H = Math.min(460, height - 60);
|
||||
const cx = width / 2;
|
||||
const cy = height / 2;
|
||||
|
||||
// ── Wrapper (ScrollFactor 0 = fiksno na ekranu) ──
|
||||
this.container = this.scene.add.container(cx, cy).setScrollFactor(0).setDepth(25000).setVisible(false);
|
||||
|
||||
// Temni tančati overlay
|
||||
this.overlay = this.scene.add.rectangle(0, 0, width * 2, height * 2, 0x000000, 0.7)
|
||||
.setScrollFactor(0).setDepth(24999).setVisible(false);
|
||||
|
||||
// ── Ozadje dnevnika (pergament) ──
|
||||
const bg = this.scene.add.graphics();
|
||||
// Senčni okvir
|
||||
bg.fillStyle(0x1a0f00, 1);
|
||||
bg.fillRoundedRect(-W / 2 + 5, -H / 2 + 5, W, H, 12);
|
||||
// Pergament
|
||||
bg.fillStyle(0x2a1a08, 1);
|
||||
bg.fillRoundedRect(-W / 2, -H / 2, W, H, 10);
|
||||
// Zlat rob
|
||||
bg.lineStyle(3, 0xc8a040, 1);
|
||||
bg.strokeRoundedRect(-W / 2, -H / 2, W, H, 10);
|
||||
// Notranji rob
|
||||
bg.lineStyle(1, 0x7a5020, 0.6);
|
||||
bg.strokeRoundedRect(-W / 2 + 8, -H / 2 + 8, W - 16, H - 16, 8);
|
||||
this.container.add(bg);
|
||||
|
||||
// ── Naslov ──
|
||||
const title = this.scene.add.text(0, -H / 2 + 28, '📖 Kaijev Dnevnik', {
|
||||
fontFamily: '"Cinzel", "Georgia", serif',
|
||||
fontSize: '22px',
|
||||
color: '#f0c060',
|
||||
stroke: '#3a1a00',
|
||||
strokeThickness: 4,
|
||||
}).setOrigin(0.5, 0.5);
|
||||
this.container.add(title);
|
||||
|
||||
// ── Razdelilna črta pod naslovom ──
|
||||
const divider = this.scene.add.graphics();
|
||||
divider.lineStyle(1, 0xc8a040, 0.6);
|
||||
divider.lineBetween(-W / 2 + 20, -H / 2 + 50, W / 2 - 20, -H / 2 + 50);
|
||||
this.container.add(divider);
|
||||
|
||||
// ── Leva stran: seznam vnosov ──
|
||||
const listX = -W / 2 + 20;
|
||||
this.entryButtons = [];
|
||||
this._entryListY0 = -H / 2 + 65;
|
||||
this._listStartX = listX;
|
||||
this._panelW = W;
|
||||
this._panelH = H;
|
||||
|
||||
// Placeholder za vnose (izgradujemo dinamično v refresh)
|
||||
this._entryListContainer = this.scene.add.container(0, 0);
|
||||
this.container.add(this._entryListContainer);
|
||||
|
||||
// ── Desna stran: vsebina ──
|
||||
this._contentX = -W / 2 + W * 0.42;
|
||||
this._contentY = -H / 2 + 65;
|
||||
this._contentW = W * 0.57;
|
||||
this._contentH = H - 90;
|
||||
|
||||
// Ločevalna črta med levo in desno
|
||||
const div2 = this.scene.add.graphics();
|
||||
div2.lineStyle(1, 0x7a5020, 0.5);
|
||||
div2.lineBetween(-W / 2 + W * 0.41, -H / 2 + 55, -W / 2 + W * 0.41, H / 2 - 15);
|
||||
this.container.add(div2);
|
||||
|
||||
// Vsebinski tekst (začasni, bo refreshan)
|
||||
this._contentTitle = this.scene.add.text(this._contentX + 8, this._contentY + 10, '', {
|
||||
fontFamily: '"Georgia", serif',
|
||||
fontSize: '16px',
|
||||
color: '#f0c060',
|
||||
wordWrap: { width: this._contentW - 20 },
|
||||
}).setOrigin(0, 0);
|
||||
this.container.add(this._contentTitle);
|
||||
|
||||
this._contentBody = this.scene.add.text(this._contentX + 8, this._contentY + 40, '', {
|
||||
fontFamily: '"Georgia", serif',
|
||||
fontSize: '13px',
|
||||
color: '#d4b872',
|
||||
wordWrap: { width: this._contentW - 20 },
|
||||
lineSpacing: 5,
|
||||
}).setOrigin(0, 0);
|
||||
this.container.add(this._contentBody);
|
||||
|
||||
// ── Gumb ZAPRI ──
|
||||
const closeBtn = this.scene.add.text(W / 2 - 15, -H / 2 + 15, '✕', {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '18px',
|
||||
color: '#c87050',
|
||||
}).setOrigin(0.5, 0.5).setInteractive({ useHandCursor: true });
|
||||
closeBtn.on('pointerdown', this.close, this);
|
||||
closeBtn.on('pointerover', () => closeBtn.setColor('#ff7744'));
|
||||
closeBtn.on('pointerout', () => closeBtn.setColor('#c87050'));
|
||||
this.container.add(closeBtn);
|
||||
|
||||
// ── Navodila (spodaj) ──
|
||||
const hint = this.scene.add.text(0, H / 2 - 15, '[J] Zapri | ←→ Strani', {
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '11px',
|
||||
color: '#7a5030',
|
||||
}).setOrigin(0.5, 1);
|
||||
this.container.add(hint);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// REFRESH: Posodobi seznam vnosov in vsebino
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
_refresh() {
|
||||
// Počisti stari seznam
|
||||
this._entryListContainer.removeAll(true);
|
||||
|
||||
const unlocked = this.entries.filter(e => e.unlocked);
|
||||
this.currentPage = Math.max(0, Math.min(this.currentPage, unlocked.length - 1));
|
||||
|
||||
unlocked.forEach((entry, i) => {
|
||||
const yOff = this._entryListY0 + i * 38;
|
||||
const isActive = i === this.currentPage;
|
||||
|
||||
// Ozadje za aktivni vnos
|
||||
if (isActive) {
|
||||
const hl = this.scene.add.graphics();
|
||||
hl.fillStyle(0x3a2010, 1);
|
||||
hl.fillRoundedRect(this._listStartX, yOff - 14, this._panelW * 0.39, 32, 6);
|
||||
hl.lineStyle(1, 0xc8a040, 0.7);
|
||||
hl.strokeRoundedRect(this._listStartX, yOff - 14, this._panelW * 0.39, 32, 6);
|
||||
this._entryListContainer.add(hl);
|
||||
}
|
||||
|
||||
const row = this.scene.add.text(this._listStartX + 10, yOff, `${entry.icon} ${entry.title}`, {
|
||||
fontFamily: '"Georgia", serif',
|
||||
fontSize: '13px',
|
||||
color: isActive ? '#f0c060' : '#a08040',
|
||||
}).setOrigin(0, 0.5).setInteractive({ useHandCursor: true });
|
||||
|
||||
row.on('pointerdown', () => {
|
||||
this.currentPage = i;
|
||||
this._refresh();
|
||||
});
|
||||
row.on('pointerover', () => row.setColor('#ffe080'));
|
||||
row.on('pointerout', () => row.setColor(isActive ? '#f0c060' : '#a08040'));
|
||||
|
||||
this._entryListContainer.add(row);
|
||||
});
|
||||
|
||||
// Prikaži vsebino aktivnega vnosa
|
||||
const active = unlocked[this.currentPage];
|
||||
if (active) {
|
||||
this._contentTitle.setText(`${active.icon} ${active.title}`);
|
||||
this._contentBody.setText(active.text.join('\n'));
|
||||
} else {
|
||||
this._contentTitle.setText('');
|
||||
this._contentBody.setText('');
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// TOGGLE / OPEN / CLOSE
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
toggle() {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.isOpen = true;
|
||||
this._refresh();
|
||||
this.container.setVisible(true);
|
||||
this.overlay.setVisible(true);
|
||||
|
||||
// Pojavi se animacija
|
||||
this.container.setScale(0.85);
|
||||
this.container.setAlpha(0);
|
||||
this.scene.tweens.add({
|
||||
targets: this.container,
|
||||
scaleX: 1, scaleY: 1, alpha: 1,
|
||||
duration: 220,
|
||||
ease: 'Back.easeOut',
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.scene.tweens.add({
|
||||
targets: this.container,
|
||||
scaleX: 0.9, scaleY: 0.9, alpha: 0,
|
||||
duration: 150,
|
||||
ease: 'Sine.easeIn',
|
||||
onComplete: () => {
|
||||
this.container.setVisible(false);
|
||||
this.overlay.setVisible(false);
|
||||
this.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// UNLOCK ENTRY (kliči od drugod: this.journalSystem.unlockEntry('first_tree'))
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
unlockEntry(id) {
|
||||
const entry = this.entries.find(e => e.id === id);
|
||||
if (!entry || entry.unlocked) return;
|
||||
|
||||
entry.unlocked = true;
|
||||
console.log(`📖 Dnevnik odklenjen: "${entry.title}"`);
|
||||
|
||||
// Toast obvestilo
|
||||
if (this.scene.inventorySystem) {
|
||||
this.scene.inventorySystem._showMsg(`📖 Nov vnos: "${entry.title}"`, '#f0c060');
|
||||
}
|
||||
}
|
||||
}
|
||||
72
nova farma TRAE/src/systems/LocalizationSystem.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* LocalizationSystem.js
|
||||
*
|
||||
* Sistem za jezike: SL (privzeto), EN, DE, IT, CN.
|
||||
* Samodejno zazna OS jezik.
|
||||
*/
|
||||
export default class LocalizationSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.supportedLangs = ['sl', 'en', 'de', 'it', 'cn'];
|
||||
this.currentLang = 'sl';
|
||||
this.translations = {};
|
||||
|
||||
// UTF-8 pisava s podporo za šumnike in kitajske pismenke
|
||||
this.fontFamily = '"Noto Sans", "Noto Sans SC", "Arial Black", sans-serif';
|
||||
}
|
||||
|
||||
init() {
|
||||
// Preveri če imamo shranjen jezik
|
||||
const savedLang = localStorage.getItem('nova_farma_lang');
|
||||
if (savedLang && this.supportedLangs.includes(savedLang)) {
|
||||
this.currentLang = savedLang;
|
||||
console.log(`🌍 Localization: Loaded saved lang: ${this.currentLang}`);
|
||||
} else {
|
||||
// Zaznaj sistemski jezik igralca
|
||||
const sysLang = navigator.language || navigator.userLanguage;
|
||||
let shortLang = sysLang.split('-')[0].toLowerCase();
|
||||
|
||||
// Preslikava zh -> cn
|
||||
if (shortLang === 'zh') shortLang = 'cn';
|
||||
|
||||
if (this.supportedLangs.includes(shortLang)) {
|
||||
this.currentLang = shortLang;
|
||||
} else {
|
||||
// Če jezika ni podprtega, se vrni na privzeto Slovenščino (po navodilu)
|
||||
this.currentLang = 'sl';
|
||||
}
|
||||
console.log(`🌍 Localization: Detected OS lang: ${sysLang}, using: ${this.currentLang}`);
|
||||
}
|
||||
|
||||
// Naloži JSON slovar iz Phaserjevega Cache-a
|
||||
if (this.scene.cache.json.exists('localization')) {
|
||||
this.translations = this.scene.cache.json.get('localization');
|
||||
} else {
|
||||
console.warn('⚠️ localization.json ni naložen v cache!');
|
||||
}
|
||||
}
|
||||
|
||||
setLanguage(lang) {
|
||||
if (this.supportedLangs.includes(lang)) {
|
||||
this.currentLang = lang;
|
||||
localStorage.setItem('nova_farma_lang', lang);
|
||||
|
||||
// Če želimo emitirati po celem game-u
|
||||
if (this.scene && this.scene.game) {
|
||||
this.scene.game.events.emit('language-changed', this.currentLang);
|
||||
}
|
||||
console.log(`🌍 Jezik spremenjen na: ${this.currentLang}`);
|
||||
}
|
||||
}
|
||||
|
||||
t(key) {
|
||||
if (this.translations[this.currentLang] && this.translations[this.currentLang][key]) {
|
||||
return this.translations[this.currentLang][key];
|
||||
}
|
||||
// Fallback na slovenščino
|
||||
if (this.translations['sl'] && this.translations['sl'][key]) {
|
||||
return this.translations['sl'][key];
|
||||
}
|
||||
return `[${key}]`; // Če ključa sploh ni
|
||||
}
|
||||
}
|
||||
144
nova farma TRAE/src/systems/TwinBondSystem.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* TwinBondSystem.js
|
||||
*
|
||||
* Sistem, ki občasno pošlje srhljiv telepatski signal (spomin/klic) od Ane.
|
||||
* Ekran naključno utripne vijolično in se izpiše skrit tekst v glavi Kaja.
|
||||
*/
|
||||
|
||||
export default class TwinBondSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.isActive = false;
|
||||
|
||||
// Overlay za vijoličen blisk
|
||||
this.flashOverlay = scene.add.graphics()
|
||||
.setDepth(9999)
|
||||
.setScrollFactor(0);
|
||||
|
||||
// Tekst na sredini zaslona
|
||||
const cx = scene.cameras.main.width / 2;
|
||||
const cy = scene.cameras.main.height / 2;
|
||||
|
||||
this.messageText = scene.add.text(cx, cy - 100, '', {
|
||||
fontFamily: 'Courier New',
|
||||
fontSize: '42px',
|
||||
color: '#ff99ff',
|
||||
stroke: '#2b002b',
|
||||
strokeThickness: 8,
|
||||
align: 'center',
|
||||
fontStyle: 'bold'
|
||||
})
|
||||
.setOrigin(0.5, 0.5)
|
||||
.setDepth(10000)
|
||||
.setScrollFactor(0)
|
||||
.setAlpha(0);
|
||||
|
||||
// Nabor Aninih sporočil
|
||||
this.messages = [
|
||||
"K a i . . . n a j d i m e . . .",
|
||||
"T u k a j d o l j e t e m a . . .",
|
||||
"N e o b u p a j . . . T w i n B o n d d e l u j e .",
|
||||
"K a i . . . b o l i m e . . .",
|
||||
"P a z i s e n o č i . . ."
|
||||
];
|
||||
|
||||
// Tipka za testiranje (tipka T)
|
||||
this.keyT = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.T);
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
// Testni sprožilec
|
||||
if (Phaser.Input.Keyboard.JustDown(this.keyT) && !this.isActive) {
|
||||
this.triggerBond();
|
||||
}
|
||||
|
||||
// Naključno sprožanje (zelo majhna možnost ob igranju)
|
||||
// 1 na 10,000 frejmov (~ vsake 3-5 minut)
|
||||
if (Math.random() < 0.0001 && !this.isActive) {
|
||||
// Samo če je zunaj spanja
|
||||
this.triggerBond();
|
||||
}
|
||||
}
|
||||
|
||||
triggerBond() {
|
||||
this.isActive = true;
|
||||
|
||||
// Oddaj event za AccessibilityManager (za Deaf Mode Purple HUD glow)
|
||||
if (this.scene.game) {
|
||||
this.scene.game.events.emit('twinBondPulse');
|
||||
}
|
||||
|
||||
// Zaustavi Kaja za kratek čas (šok)
|
||||
if (this.scene.kai) {
|
||||
this.scene.kai.canMove = false;
|
||||
this.scene.kai.setVelocity(0, 0);
|
||||
}
|
||||
|
||||
// 1. Zvok (Web Audio API srhljiv glitch freq)
|
||||
this._playGlitchSound();
|
||||
|
||||
// 2. Narisemo purple flash, ki pokrije zaslon
|
||||
const W = this.scene.cameras.main.width;
|
||||
const H = this.scene.cameras.main.height;
|
||||
this.flashOverlay.clear();
|
||||
|
||||
let alpha = this.scene.accessState?.autismMode ? 0.2 : 0.85; // Omehčan za avtizem
|
||||
this.flashOverlay.fillStyle(0x3a0055, alpha); // Temno vijolična
|
||||
this.flashOverlay.fillRect(0, 0, W, H);
|
||||
|
||||
// Skrijemo čez 0.15s
|
||||
this.scene.time.delayedCall(150, () => {
|
||||
this.flashOverlay.clear();
|
||||
});
|
||||
|
||||
// 3. Izberi random text in naredi typewriter
|
||||
const text = Phaser.Utils.Array.GetRandom(this.messages);
|
||||
this.messageText.setText('');
|
||||
this.messageText.setAlpha(1);
|
||||
|
||||
let index = 0;
|
||||
const typeTimer = this.scene.time.addEvent({
|
||||
delay: 80,
|
||||
repeat: text.length - 1,
|
||||
callback: () => {
|
||||
this.messageText.setText(text.substring(0, index + 1));
|
||||
index++;
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Fade out text in enable movement po 3 sekundah
|
||||
this.scene.time.delayedCall(3000, () => {
|
||||
this.scene.tweens.add({
|
||||
targets: this.messageText,
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
onComplete: () => {
|
||||
this.isActive = false;
|
||||
if (this.scene.kai) {
|
||||
this.scene.kai.canMove = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_playGlitchSound() {
|
||||
if (!this.scene.sound.context) return;
|
||||
const ctx = this.scene.sound.context;
|
||||
const osc = ctx.createOscillator();
|
||||
const gain = ctx.createGain();
|
||||
|
||||
// Srhljiv nizek zvok s frekvenčno modulacijo
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(120, ctx.currentTime);
|
||||
osc.frequency.linearRampToValueAtTime(40, ctx.currentTime + 1.5);
|
||||
|
||||
gain.gain.setValueAtTime(0.3, ctx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 1.5);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(ctx.destination);
|
||||
osc.start();
|
||||
osc.stop(ctx.currentTime + 1.5);
|
||||
}
|
||||
}
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
534
nova farma TRAE/src/systems/ZombieSystem.js
Normal file
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 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 = 2; // Začne z 2, raste po dnevih
|
||||
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])
|
||||
this._nightCount = 0; // Koliko noči je minilo
|
||||
|
||||
// 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._nightCount++;
|
||||
|
||||
// Stopnjevanje tezje po vsaki noci
|
||||
// Povečana nevarnost: 2x močnejši, večja hitrost in večje število
|
||||
this.maxZombies = Math.min(4 + this._nightCount * 2, 16);
|
||||
const spawnDelay = Math.max(3000, 8000 - this._nightCount * 1000);
|
||||
this.zombieSpeed = Math.min(60 + this._nightCount * 5, 100);
|
||||
|
||||
console.log(`🌙 Noč ${this._nightCount}: max=${this.maxZombies}, delay=${spawnDelay}ms, speed=${this.zombieSpeed}px/s`);
|
||||
|
||||
this.spawnTimer = this.scene.time.addEvent({
|
||||
delay: spawnDelay,
|
||||
loop: true,
|
||||
callback: () => {
|
||||
if (this.zombies.length < this.maxZombies) {
|
||||
this.spawnZombie();
|
||||
}
|
||||
// Samo ponoči: 30% možnost za spawn Rakuna na robu mape
|
||||
if (Math.random() < 0.3) {
|
||||
this.spawnRaccoon();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Takoj spawni prvega
|
||||
this.scene.time.delayedCall(2000, () => {
|
||||
if (this.zombies.length < this.maxZombies) this.spawnZombie();
|
||||
});
|
||||
|
||||
// Journal unlock ob prvi noči
|
||||
if (this._nightCount === 1 && this.scene.journalSystem) {
|
||||
this.scene.time.delayedCall(3000, () => {
|
||||
this.scene.journalSystem.unlockEntry('first_night');
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// SPAWN RAKUNA (Nočni spawn)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
spawnRaccoon(x = null, y = null) {
|
||||
if (x === null || y === null) {
|
||||
const edge = Phaser.Math.Between(0, 3);
|
||||
const margin = 100;
|
||||
switch (edge) {
|
||||
case 0: x = this.islandX + Phaser.Math.Between(margin, this.islandW - margin); y = this.islandY + margin; break;
|
||||
case 1: x = this.islandX + this.islandW - margin; y = this.islandY + Phaser.Math.Between(margin, this.islandH - margin); break;
|
||||
case 2: x = this.islandX + Phaser.Math.Between(margin, this.islandW - margin); y = this.islandY + this.islandH - margin; break;
|
||||
case 3: x = this.islandX + margin; y = this.islandY + Phaser.Math.Between(margin, this.islandH - margin); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Uporabimo placeholder zombi sprite, dokler ni rakuna (tint na sivo, pomanjšan)
|
||||
let tex = this.scene.textures.exists('raccoon') ? 'raccoon' : 'zombie_shambler';
|
||||
const sprite = this.scene.add.sprite(x, y, tex, 0)
|
||||
.setScale(tex === 'raccoon' ? 1.0 : 0.3)
|
||||
.setOrigin(0.5, 1.0)
|
||||
.setDepth(y);
|
||||
|
||||
if (tex === 'zombie_shambler') sprite.setTint(0x777777); // Sivo-črna barva za rakuna
|
||||
|
||||
sprite.setAlpha(0);
|
||||
this.scene.tweens.add({ targets: sprite, alpha: 1, duration: 800 });
|
||||
|
||||
const halo = this.scene.add.graphics().setDepth(y - 1);
|
||||
halo.fillStyle(0xffaa00, 0.15); // Oranžen halo za rakune
|
||||
halo.fillCircle(x, y - 10, 20);
|
||||
|
||||
const raccoon = {
|
||||
sprite, halo, x, y, state: 'wandering', tamed: false,
|
||||
hp: 10, maxHp: 10, dir: 'down',
|
||||
wanderTimer: 0, wanderDirX: 0, wanderDirY: 1,
|
||||
isRaccoon: true
|
||||
};
|
||||
|
||||
this.zombies.push(raccoon);
|
||||
console.log(`🦝 Rakun spawnan ponoči @ ${Math.round(x)},${Math.round(y)}`);
|
||||
return raccoon;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// 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) ali oranžen za rakune
|
||||
z.halo.clear();
|
||||
z.halo.fillStyle(z.isRaccoon ? 0xffaa00 : 0xff0000, 0.18);
|
||||
z.halo.fillCircle(z.sprite.x, z.sprite.y - 20, z.isRaccoon ? 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();
|
||||
}
|
||||
}
|
||||