Compare commits

..

6 Commits

Author SHA1 Message Date
325ed52479 neki bo 2026-04-26 23:25:37 +02:00
b660429e3c feat: Faza 1 Gameplay Loop - Kista, Sekanje, Gradnja, Kmetovanje, Dnevnik, Zombiji
- Starter Chest (Kista): Nov sprite asset (chest_closed.png), zamenjal rectangles z dejansko sliko, dodal axe reward ob odprtju
- Sekanje dreves: Zahteva Sekiro v rokah, padec drevesa odvrze 3 kose lesa (pickup sistem), unlock dnevniska vnosa
- Gradnja Barikad [B]: Nov wooden_fence.png asset, BuildingSystem posodobljen z leseno barikado (cena: 2 les), ghost preview z grid snappingom, deductItem() API
- Farming zanka: Popravljen seed property v InventorySystem, unlock ob prvem sajenju in zetvi
- Kaijev Dnevnik [J]: Novi JournalSystem.js s 6 vnosi, gotski UI z zlatimi okviri, animirano odpiranje, odklepanje ob napredku
- Zombie nochni napadi: Stopnjevano po nochih (max 8, hitrost do 65px/s, krajsi spawn delay), unlock dnevnika ob prvi nochi
- InventorySystem: Popravljen bug kjer tools niso bili dodani v hotbar, dodan axe in wood v ITEM_DEFS, nova deductItem() funkcija
- Pomoznna remove_bg.py skripta za odstranjevanje belega ozadja assetov
2026-03-13 20:11:46 +01:00
188bd769cf 🔥 FAZE SPRINT 1-3 COMPLETION: DayNight, Inventory, Core Farming System, Mrtvi Zombiji (ShamblerAI & Alfa Moć Taming), Sredili assets & daily UI 2026-03-03 17:46:41 +01:00
35153eeacf docs: dev log 2026-03-02 (dež + BuildingSystem) 2026-03-02 20:43:37 +01:00
4cbb198d7a feat: dež po celi mapi + BuildingSystem [2026-03-02]
RAIN SYSTEM:
- Zamenjal sprite particle emitter z procedural canvas rain (Graphics)
- 160 naključnih kapljic kot kratke diagonalne črtice (8-18px)
- Hitrost 420-700 px/s, nagib v levo (kot pravi dež)
- Alpha 0.25-0.55 — prozorno, otok viden
- Brez vzorcev / 'stebrov' — pravo Math.random() razmestitev
- Odpravil bug: dež je zdaj po celi mapi (sledil Kai-u)
- Odpravil SyntaxError: viewW dvojna deklaracija → rainViewW

BUILDING SYSTEM (src/systems/BuildingSystem.js):
- Nov modul BuildingSystem.js
- [B] tipka odpre/zapre Build Mode
- Stranski meni z ikonami: Šotor, Ogenj, Zbirač dežja, Temelj
- Ghost preview (zelena = ok, rdeča = zunaj otoka)
- Postavitev z klikom na otok
- Y-sorting: Kai se skrije za stavbami ko gre zadaj
- Fizični collider (static body) — Kai ne more skozi stavbe
- Spawn animacija (Back.out)
- Preloadan foundation_concrete.png
2026-03-02 20:42:49 +01:00
1d7aaf9562 💾 Morning Session Complete - Pixel-Perfect + Auto-Loaders
SESSION DATE: 11. Februar 2026, 06:27 - 10:05
DURATION: ~3.5 hours
FOCUS: Pixel-perfect scale system + Auto-loaders

 IMPLEMENTED SYSTEMS:

1. PIXEL-PERFECT SCALE (06:30):
   - Base tile: 32x32 px reference
   - Kai: 64 px height (2 tiles) - FORCED
   - Tall grass: 28 px height - FORCED
   - Oak tree: 160 px height - FORCED
   - Ratio: Kai 2.3x taller than grass ✓

2. VEGETATION GROUNDING (06:35):
   - All plants: setOrigin(0.5, 1.0) bottom-center
   - ROOT_OFFSET: 6px burial into ground
   - Y-depth sorting: setDepth(this.y) for all
   - Result: NO FLOATING! Plants grow from ground!

3. AUTO-LOADER SYSTEM (06:44):
   - loadVegetation(): 28px target, 4px burial
   - loadTree(): 160px target, 8px burial
   - Auto-scale calculation: target/source
   - SMOOTH rendering: LINEAR filter
   - NO manual setup needed!

4. TEST OBJECTS:
   - Test grass: Left of Kai (28px, 4px burial)
   - Test oak: Right of Kai (160px, 8px burial)
   - Both demonstrate perfect grounding

TECHNICAL CHANGES:
- Kai origin: 0.5, 0.9 → 0.5, 1.0
- Kai scale: 1.0 → 0.25 (64px exact)
- Vegetation re-enabled with grounding
- Rain system active

FILES MODIFIED:
- src/scenes/GrassScene_Clean.js (major refactor)
- dokumentacija/TODO_NEXT_SESSION.md (created)

COMMITS THIS SESSION: 3
1. Pixel-perfect scale constants
2. Vegetation grounding system
3. Auto-loader implementation

Status: All systems working, ready for testing!

Date: 11. Februar 2026, 10:05
2026-02-11 10:05:36 +01:00
63 changed files with 7186 additions and 108 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
assets/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
nova farma TRAE/assets/DEMO_FAZA1/Trees/drevo_faza_2.png Normal file → Executable file

Binary file not shown.

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

View 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 (818px)
- Hitrost: 420700 px/s (hiter, naraven)
- Alpha: 0.250.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*

View File

@@ -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.

View 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*

View 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 030 (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 1200)
**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 1200 │ 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*

View 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.25×) | ✅ 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" (~23 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" (~34 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" (~23 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" (~12 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`*

View 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
---

View File

@@ -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 1200)
**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 6175)
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`*

View File

@@ -4,6 +4,10 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Nova Farma - Clean Start</title> <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> <style>
body { body {
margin: 0; margin: 0;

View 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": "⬇️ 你当前的目标:给小麦浇水! ⬇️"
}
}

View 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])

View File

@@ -3,6 +3,7 @@ if (typeof process !== 'undefined' && process.env) {
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'; process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
} }
import MenuScene from './scenes/MenuScene.js';
import GrassScene from './scenes/GrassScene_Clean.js'; import GrassScene from './scenes/GrassScene_Clean.js';
import UIScene from './scenes/UIScene.js'; import UIScene from './scenes/UIScene.js';
@@ -14,8 +15,8 @@ const config = {
mode: Phaser.Scale.FIT, mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH autoCenter: Phaser.Scale.CENTER_BOTH
}, },
pixelArt: true, // Zagotovi ostrino pri zumiranju (pixel art style) pixelArt: false, // Meni uporablja smooth slike
backgroundColor: '#1a1a1a', // Temno siva, da slika izstopa backgroundColor: '#000000',
parent: 'body', parent: 'body',
physics: { physics: {
default: 'arcade', default: 'arcade',
@@ -24,7 +25,9 @@ const config = {
gravity: { y: 0 } gravity: { y: 0 }
} }
}, },
scene: [GrassScene, UIScene] // Vrstni red: MenuScene je prva (avtomatski start), ostale so pasivne
scene: [MenuScene, GrassScene, UIScene]
}; };
const game = new Phaser.Game(config); const game = new Phaser.Game(config);

View File

@@ -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 { export default class GrassSceneClean extends Phaser.Scene {
constructor() { constructor() {
super({ key: 'GrassSceneClean' }); super({ key: 'GrassSceneClean' });
@@ -20,6 +29,26 @@ export default class GrassSceneClean extends Phaser.Scene {
this.LAYER_TREES = 2; // Trees this.LAYER_TREES = 2; // Trees
this.LAYER_CHARACTERS = 10; // Kai, zombies 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 === // === GROWTH STATE ===
this.soilGrid = []; // 50x50 grid for moisture tracking this.soilGrid = []; // 50x50 grid for moisture tracking
this.grassTiles = []; // Dynamic grass instances 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('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png');
this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png'); this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png');
this.load.image('sleeping_bag', 'DEMO_FAZA1/Items/spalna_vreca.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 // 7. NEW: Gronk & Structures
this.load.spritesheet('gronk', 'DEMO_FAZA1/Characters/gronk_walk_sheet.png', { this.load.spritesheet('gronk', 'DEMO_FAZA1/Characters/gronk_walk_sheet.png', {
@@ -97,9 +127,117 @@ export default class GrassSceneClean extends Phaser.Scene {
frameHeight: 256 frameHeight: 256
}); });
this.load.image('rain_catcher', 'DEMO_FAZA1/Structures/rain_catcher.png'); 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 // 8. Weather System
this.load.image('rain_drops', 'DEMO_FAZA1/Environment/rain_drops.png'); this.load.image('rain_drops', 'DEMO_FAZA1/Environment/rain_drops.png');
// 9. Farming System Assets
FarmingSystem.preload(this);
// 10. Water System Assets
WaterSystem.preload(this);
// 11. Zombie System Assets
ZombieSystem.preload(this);
// 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() { create() {
@@ -155,12 +293,12 @@ export default class GrassSceneClean extends Phaser.Scene {
this.cameras.main.setBackgroundColor('#001d3d'); // Deep Blue Ocean this.cameras.main.setBackgroundColor('#001d3d'); // Deep Blue Ocean
// --- ZOOM CONTROL --- // --- 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) => { this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
const zoomFactor = -0.001; const zoomFactor = -0.001;
const newZoom = this.cameras.main.zoom + deltaY * zoomFactor; 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 --- // --- 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. // FAILSAFE 2: Use simple Rectangles instead of TileSprites/Textures to STOP OOM.
// We will add waves later if stability allows. // We will add waves later if stability allows.
const viewW = this.scale.width || 1280; // Ustvarimo dva velika modra pravokotnika kot 'bazen', v katerem je otok
const viewH = this.scale.height || 720; this.oceanLayer1 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x004488)
this.oceanLayer1 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x004488)
.setScrollFactor(0)
.setDepth(-290) .setDepth(-290)
.setAlpha(0.5); .setAlpha(0.5);
// Optional: Second layer for depth effect // Optional: Second layer for depth effect
this.oceanLayer2 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x002244) this.oceanLayer2 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x002244)
.setScrollFactor(0)
.setDepth(-295) // Behind .setDepth(-295) // Behind
.setAlpha(1.0); .setAlpha(1.0);
@@ -540,7 +674,9 @@ export default class GrassSceneClean extends Phaser.Scene {
/* /*
// Trnje (Thorns) - Draggable // Trnje (Thorns) - Draggable
this.trnje = this.add.image(startX - 200, startY + 100, 'trnje'); 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.setScale(0.5); // Adjust scale if needed
this.trnje.setDepth(this.trnje.y); // Y-sorting
this.trnje.setInteractive({ draggable: true }); this.trnje.setInteractive({ draggable: true });
// Trigger Amnesia Clear on interaction // Trigger Amnesia Clear on interaction
this.trnje.on('pointerdown', () => { this.trnje.on('pointerdown', () => {
@@ -549,9 +685,10 @@ export default class GrassSceneClean extends Phaser.Scene {
// --- NEW: RAIN CATCHER --- // --- NEW: RAIN CATCHER ---
this.rainCatcher = this.physics.add.image(startX + 150, startY + 50, '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.setScale(0.8);
this.rainCatcher.setInteractive({ draggable: true }); 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); this.rainCatcher.body.setImmovable(true);
// Collider added later with Kai // Collider added later with Kai
*/ */
@@ -826,10 +963,19 @@ export default class GrassSceneClean extends Phaser.Scene {
const SPAWN_Y = WORLD_H / 2; const SPAWN_Y = WORLD_H / 2;
this.kai = this.physics.add.sprite(SPAWN_X, SPAWN_Y, 'kai'); 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.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 // RESPAWN SYSTEM
this.respawnPoint = { x: SPAWN_X, y: SPAWN_Y }; 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, () => { this.physics.add.overlap(this.kai, this.sleepingBag, () => {
if (this.lastCheckpointTime && this.time.now - this.lastCheckpointTime < 5000) return; 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 // Adjust Physics Body for larger size
// Width ~40, Height ~30 (relative to scaled sprite) // Width ~40, Height ~30 (relative to scaled sprite)
@@ -867,8 +1110,9 @@ export default class GrassSceneClean extends Phaser.Scene {
// REMOVED PER USER REQUEST // REMOVED PER USER REQUEST
/* /*
this.gronk = this.physics.add.sprite(startX - 150, startY - 100, 'gronk'); 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.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.setImmovable(true);
this.gronk.body.setSize(80, 60); this.gronk.body.setSize(80, 60);
this.gronk.body.setOffset(88, 190); // Adjusted for 256x256 frame this.gronk.body.setOffset(88, 190); // Adjusted for 256x256 frame
@@ -925,8 +1169,11 @@ export default class GrassSceneClean extends Phaser.Scene {
this.kai.stop(); this.kai.stop();
// Camera setup logic // 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 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(); this.cursors = this.input.keyboard.createCursorKeys();
// Add WASD keys // Add WASD keys
this.keys = this.input.keyboard.addKeys({ 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); if (this.riverCollider) this.physics.add.collider(this.kai, this.riverCollider);
// this.physics.add.collider(this.kai, this.obstaclesGroup); // 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 --- // --- ANIMATIONS ---
// 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up // 0-3: Down, 4-7: Left, 8-11: Right, 12-15: Up
// This is a duplicate animation creation block, removing it. // 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 === // === FARMING & FOG REMOVED - Ultra Clean ===
// === PROCEDURAL GROWTH SYSTEM INITIALIZATION === // === PROCEDURAL GROWTH SYSTEM INITIALIZATION ===
// TEMPORARILY DISABLED FOR TESTING
/*
console.log('🌱 Initializing Procedural Growth System...'); console.log('🌱 Initializing Procedural Growth System...');
// Initialize soil moisture grid // 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('✅ Growth system ready - Island will evolve over time!');
*/
console.log('🚫 Vegetation DISABLED for testing');
// === RAIN WEATHER SYSTEM === // === RAIN WEATHER SYSTEM (Procedural — natural looking) ===
// Create rain particles that fall from sky // We draw rain as short diagonal lines each frame on a Graphics object.
this.rainParticles = this.add.particles(0, -50, 'rain_drops', { // This avoids the "vertical column" pattern of sprite-based emitters.
x: { min: 0, max: this.scale.width }, this.rainGraphics = this.add.graphics()
y: -50, .setScrollFactor(0) // Fixed to screen
speedY: { min: 300, max: 500 }, .setDepth(5000);
speedX: { min: -20, max: 20 },
lifespan: 3000, // Rain drop pool — randomised positions, speeds, lengths, and alpha
scale: { min: 0.3, max: 0.6 }, const DROP_COUNT = 160; // Sparse enough to see island behind it
alpha: { start: 0.7, end: 0.3 }, const SCREEN_W = this.cameras.main.width;
frequency: 50, const SCREEN_H = this.cameras.main.height;
rotate: { min: -10, max: 10 },
blendMode: 'ADD', this.rainDrops = [];
emitting: true 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, // 420700 px/s — fast
speedX: -40 + Math.random() * 20, // Slight left slant
length: 8 + Math.random() * 10, // Short: 818px
alpha: 0.25 + Math.random() * 0.30, // 0.250.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!"); console.log("SFX: THUD!");
// this.sound.play('tree_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); this.cameras.main.shake(300, 0.005);
}
tree.destroy(); 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) // 3. AUTO-REPLANT (Spawn Grass Clump)
this.spawnReplant(tx, ty); // Use generic regrowth for consistency if desired, or keep as specialized spawn 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 // Check if already placed here
if (placed.some(p => p.gridX === gridX && p.gridY === gridY)) continue; 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 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) // Create sapling (small tree)
const sapling = this.add.image(worldX, worldY, 'drevo_faza_1') const treeScale = this.PIXEL_OAK_HEIGHT / this.SOURCE_TREE_AVG; // Pixel-perfect oak scale
.setScale(0.4) // Small sapling const sapling = this.add.image(worldX, tileBottomY + this.ROOT_OFFSET, 'drevo_faza_1')
.setDepth(this.LAYER_TREES) .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); .setAlpha(0.8);
this.treeSaplings.push({ this.treeSaplings.push({
@@ -1374,14 +1908,16 @@ export default class GrassSceneClean extends Phaser.Scene {
this.treeSaplings.forEach(tree => { this.treeSaplings.forEach(tree => {
tree.age++; tree.age++;
const treeScale = this.PIXEL_OAK_HEIGHT / this.SOURCE_TREE_AVG;
// Growth stages: 1→2 at 3 days, 2→3 at 7 days // Growth stages: 1→2 at 3 days, 2→3 at 7 days
if (tree.age === 3 && tree.stage === 1) { if (tree.age === 3 && tree.stage === 1) {
tree.stage = 2; 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++; treesGrown++;
} else if (tree.age === 7 && tree.stage === 2) { } else if (tree.age === 7 && tree.stage === 2) {
tree.stage = 3; 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++; treesGrown++;
} }
}); });
@@ -1394,15 +1930,19 @@ export default class GrassSceneClean extends Phaser.Scene {
/** Grow grass at grid position */ /** Grow grass at grid position */
growGrass(gridX, gridY) { growGrass(gridX, gridY) {
const worldX = this.islandX + (gridX * this.TILE_SIZE) + (this.TILE_SIZE / 2); 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) // Random grass sprite (use available grass assets)
const grassTypes = ['grass_ref_1', 'grass_cluster_dense', 'grass_cluster_flowery']; const grassTypes = ['grass_ref_1', 'grass_cluster_dense', 'grass_cluster_flowery'];
const grassType = Phaser.Utils.Array.GetRandom(grassTypes); const grassType = Phaser.Utils.Array.GetRandom(grassTypes);
const grass = this.add.image(worldX, worldY, grassType) // PIXEL-PERFECT SCALE: Tall grass = 28px height
.setScale(0.6 + Math.random() * 0.4) // Size variation const grassScale = this.PIXEL_GRASS_TALL_AVG / this.SOURCE_GRASS_TALL;
.setDepth(this.LAYER_VEGETATION) // Layer 1 - below characters!
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); .setAlpha(0);
// Fade in animation // Fade in animation
@@ -1439,6 +1979,26 @@ export default class GrassSceneClean extends Phaser.Scene {
/* Removed for cleanup */ /* Removed for cleanup */
update(time, delta) { 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 --- // --- ANIMATE OCEAN ---
// (Parallax skipped for rectangles, logic removed to prevent errors) // (Parallax skipped for rectangles, logic removed to prevent errors)
// --- ANIMATE FOAM --- // --- ANIMATE FOAM ---
@@ -1486,14 +2046,33 @@ export default class GrassSceneClean extends Phaser.Scene {
}); });
// --- PLAYER MOVEMENT (SMOOTHENED) --- // --- PLAYER MOVEMENT (SMOOTHENED) ---
if (this.kai.canMove === false) {
this.kai.setVelocity(0, 0);
this.kai.anims.stop();
} else {
const speed = 500; const speed = 500;
const velocity = new Phaser.Math.Vector2(0, 0); const velocity = new Phaser.Math.Vector2(0, 0);
// Input helpers // Input helpers
const left = this.cursors.left.isDown || this.keys.left.isDown; let left = this.cursors.left.isDown || this.keys.left.isDown;
const right = this.cursors.right.isDown || this.keys.right.isDown; let right = this.cursors.right.isDown || this.keys.right.isDown;
const up = this.cursors.up.isDown || this.keys.up.isDown; let up = this.cursors.up.isDown || this.keys.up.isDown;
const down = this.cursors.down.isDown || this.keys.down.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; const space = this.cursors.space.isDown;
if (left) velocity.x = -1; if (left) velocity.x = -1;
@@ -1516,9 +2095,79 @@ export default class GrassSceneClean extends Phaser.Scene {
this.kai.setVelocity(0, 0); this.kai.setVelocity(0, 0);
this.kai.anims.stop(); this.kai.anims.stop();
} }
}
// === ULTRA CLEAN: Only Kai depth sorting === // === ULTRA CLEAN: Only Kai depth sorting ===
this.kai.setDepth(this.kai.y); 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);
});
}
} }
} }

View 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);
}
}
}

View File

@@ -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 { export default class UIScene extends Phaser.Scene {
constructor() { constructor() {
super({ key: 'UIScene', active: false }); super({ key: 'UIScene', active: false }); // Zaženemo ročno iz GrassScene
// Stanje igre (posodablja se iz GrassScene prek eventos)
this.gameState = {
hp: 100,
maxHp: 100,
energy: 100,
maxEnergy: 100,
day: 1,
hour: 6,
minute: 0,
weather: 'rain', // 'sun', 'rain', 'storm', 'cloud'
inventoryOpen: false,
hotbarItems: [null, null, null, null, null, null, null], // 7 slotov
activeSlot: 0,
isFullGame: false,
accessibility: {
deafMode: false, autismMode: false, adhdMode: false,
fontMode: false, contrastMode: false, colorblindMode: false, onehandMode: false
}
};
} }
preload() { preload() {
this.load.path = 'assets/'; this.load.path = 'assets/';
// Load UI Assets here (moved from GrassScene)
// ── Vse UI assete naložimo tukaj ──
this.load.image('ui_health_bar', 'DEMO_FAZA1/UI/health_bar.png'); this.load.image('ui_health_bar', 'DEMO_FAZA1/UI/health_bar.png');
this.load.image('ui_weather_widget', 'DEMO_FAZA1/UI/weather_widget.png'); this.load.image('ui_health_critical', 'DEMO_FAZA1/UI/health_critical.png');
this.load.image('ui_weather', 'DEMO_FAZA1/UI/weather_widget.png');
this.load.image('ui_minimap', 'DEMO_FAZA1/UI/minimap_frame.png'); this.load.image('ui_minimap', 'DEMO_FAZA1/UI/minimap_frame.png');
this.load.image('ui_hotbar', 'DEMO_FAZA1/UI/hotbar_background.png'); this.load.image('ui_hotbar', 'DEMO_FAZA1/UI/hotbar_background.png');
this.load.image('ui_slot', 'DEMO_FAZA1/UI/inventory_slot.png');
this.load.image('ui_action_btn', 'DEMO_FAZA1/UI/action_btn.png'); this.load.image('ui_action_btn', 'DEMO_FAZA1/UI/action_btn.png');
this.load.image('ui_badge_trial', 'DEMO_FAZA1/UI/badge_trial.png'); this.load.image('ui_badge_trial', 'DEMO_FAZA1/UI/badge_trial.png');
this.load.image('ui_badge_buy', 'DEMO_FAZA1/UI/badge_purchase.png');
this.load.image('ui_inventory', 'DEMO_FAZA1/UI/inventory_panel.png');
this.load.image('ui_dialog', 'DEMO_FAZA1/UI/dialog_panel.png');
} }
create() { create() {
// Simple UI Layout const W = this.cameras.main.width; // 1920
const width = this.cameras.main.width; const H = this.cameras.main.height; // 1080
const height = this.cameras.main.height; const PAD = 24;
const PAD = 60; // Increased padding for better visibility
// 0. TRIAL BADGE (Top Center - The "Hook") // ─── INITIALIZE LOCALIZATION ───
this.add.image(width / 2, PAD, 'ui_badge_trial') this.i18n = new LocalizationSystem(this);
.setOrigin(0.5, 0) this.i18n.init();
.setScale(1.0) // Increased scale
.setDepth(100); // Ensure it's on top
// 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) // Trial Version Badge
let weather = this.add.image(width - PAD, PAD, 'ui_weather_widget').setOrigin(1, 0).setScale(1.0); this.trialBadge = this.add.image(PAD, PAD, 'ui_badge_trial')
.setOrigin(0, 0)
.setScale(0.38)
.setDepth(1000);
// 3. Minimap (Bottom Right) // Badge lebdi animacija
let minimap = this.add.image(width - PAD, height - PAD, 'ui_minimap').setOrigin(1, 1).setScale(0.9); this.tweens.add({
targets: this.trialBadge,
y: PAD + 6,
duration: 1800,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut',
});
// 4. Hotbar (Bottom Center) // ── NAKUP IGRE LOGIKA ──
let hotbar = this.add.image(width / 2, height - PAD, 'ui_hotbar').setOrigin(0.5, 1).setScale(1.0); this.trialBadge.setInteractive({ useHandCursor: true });
this.trialBadge.on('pointerdown', () => {
if (!this.gameState.isFullGame) {
this.unlockFullGame();
}
});
// 5. Action Button (Right of Hotbar) // ── HP Bar (health_bar.png je vertikalni triptih: full/empty/empty) ─
let action = this.add.image(width / 2 + 400, height - PAD, 'ui_action_btn').setOrigin(0.5, 1).setScale(1.0); // 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 // Ozadje HP (temno rdeče)
this.add.text(width / 2, 50, 'HUD LAYER ACTIVE', { this.hpBg = this.add.graphics().setDepth(1001);
fontSize: '24px', fill: '#00ff00', stroke: '#000', strokeThickness: 4 this.hpBg.fillStyle(0x4a0000, 1.0);
}).setOrigin(0.5); 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);
}
} }
} }

View 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; }
}
});
}
}

View 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,
});
}
}
}

View 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();
}
}

View 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 = [];
}
}

View 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,
};
}
}

View 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');
}
}
}

View 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
}
}

View 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);
}
}

View 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 = [];
}
}

View 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();
}
}