This commit is contained in:
2026-04-26 23:25:37 +02:00
parent b660429e3c
commit 325ed52479
16 changed files with 838 additions and 78 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.

View File

@@ -4,6 +4,10 @@
<head>
<meta charset="UTF-8">
<title>Nova Farma - Clean Start</title>
<!-- Google Fonts za UTF-8 in šumnike -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
margin: 0;

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

@@ -293,12 +293,12 @@ export default class GrassSceneClean extends Phaser.Scene {
this.cameras.main.setBackgroundColor('#001d3d'); // Deep Blue Ocean
// --- ZOOM CONTROL ---
this.cameras.main.setZoom(0.8);
this.cameras.main.setZoom(1.0); // Privzet zoom za Demo
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
const zoomFactor = -0.001;
const newZoom = this.cameras.main.zoom + deltaY * zoomFactor;
this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.2, 5.0));
this.cameras.main.setZoom(Phaser.Math.Clamp(newZoom, 0.5, 2.5)); // Omejitev 0.5 do 2.5
});
// --- 1. DYNAMIC OCEAN & ISLAND SYSTEM ---
@@ -674,7 +674,9 @@ export default class GrassSceneClean extends Phaser.Scene {
/*
// Trnje (Thorns) - Draggable
this.trnje = this.add.image(startX - 200, startY + 100, 'trnje');
this.trnje.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
this.trnje.setScale(0.5); // Adjust scale if needed
this.trnje.setDepth(this.trnje.y); // Y-sorting
this.trnje.setInteractive({ draggable: true });
// Trigger Amnesia Clear on interaction
this.trnje.on('pointerdown', () => {
@@ -683,9 +685,10 @@ export default class GrassSceneClean extends Phaser.Scene {
// --- NEW: RAIN CATCHER ---
this.rainCatcher = this.physics.add.image(startX + 150, startY + 50, 'rain_catcher');
this.rainCatcher.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
this.rainCatcher.setScale(0.8);
this.rainCatcher.setInteractive({ draggable: true });
this.rainCatcher.setDepth(startY + 50); // Y-sort
this.rainCatcher.setDepth(this.rainCatcher.y); // Y-sort
this.rainCatcher.body.setImmovable(true);
// Collider added later with Kai
*/
@@ -993,21 +996,35 @@ export default class GrassSceneClean extends Phaser.Scene {
});
*/
// ─── SPALNA VREČA & STARTER CHEST (Kišta) ────────────────────────
this.sleepingBag = this.physics.add.image(SPAWN_X + 150, 5600, 'sleeping_bag');
this.sleepingBag.setOrigin(0.5, 1.0);
this.sleepingBag.setScale(0.3);
this.sleepingBag.setDepth(this.sleepingBag.y);
// ─── 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
this.starterChest = this.physics.add.image(SPAWN_X - 100, 5600, 'chest_closed');
this.starterChest.setOrigin(0.5, 1.0);
// 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(5600);
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(SPAWN_X - 100, 5600 - 100, '[E] Odpri Kišto', { fontFamily: 'Arial Black', fontSize: '13px', color: '#ffdd00', stroke: '#000', strokeThickness: 4, align: 'center' }).setOrigin(0.5, 0.5);
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', () => {
@@ -1041,43 +1058,42 @@ export default class GrassSceneClean extends Phaser.Scene {
}
});
// ─────────────────────────────────────────────────────────────────
// === RANDOM TREE GENERATION (AUTO-LOADER) ===
// === 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_COUNT = 40;
for (let i = 0; i < TREE_COUNT; i++) {
// Place on random edge to keep center farming area free
const edge = Math.floor(Math.random() * 4);
let tx, ty;
const PADDING = 200;
switch (edge) {
case 0: // Top
tx = this.islandX + Math.random() * this.islandWidth;
ty = this.islandY + PADDING + Math.random() * 300;
break;
case 1: // Right
tx = this.islandX + this.islandWidth - PADDING - Math.random() * 300;
ty = this.islandY + Math.random() * this.islandHeight;
break;
case 2: // Bottom
tx = this.islandX + Math.random() * this.islandWidth;
ty = this.islandY + this.islandHeight - PADDING - Math.random() * 300;
break;
case 3: // Left
tx = this.islandX + PADDING + Math.random() * 300;
ty = this.islandY + Math.random() * this.islandHeight;
break;
}
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);
t.setMask(this.islandMask);
this.treesGroup.add(t);
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);
// ===================================================================
@@ -1094,8 +1110,9 @@ export default class GrassSceneClean extends Phaser.Scene {
// REMOVED PER USER REQUEST
/*
this.gronk = this.physics.add.sprite(startX - 150, startY - 100, 'gronk');
this.gronk.setOrigin(0.5, 1.0); // Sidrišče čisto na dno
this.gronk.setScale(0.8); // Gronk is big!
this.gronk.setDepth(startY - 100);
this.gronk.setDepth(this.gronk.y); // Y-sorting
this.gronk.setImmovable(true);
this.gronk.body.setSize(80, 60);
this.gronk.body.setOffset(88, 190); // Adjusted for 256x256 frame
@@ -1152,8 +1169,11 @@ export default class GrassSceneClean extends Phaser.Scene {
this.kai.stop();
// Camera setup logic
this.cameras.main.startFollow(this.kai, true, 0.1, 0.1);
this.cameras.main.startFollow(this.kai, true, 0.08, 0.08); // Zelo mehko sledenje
this.cameras.main.setRoundPixels(true); // Prevent jitter
// Spremenljivka za gladko drsenje kamere naprej
this.cameraLookahead = { x: 0, y: 0 };
this.cursors = this.input.keyboard.createCursorKeys();
// Add WASD keys
this.keys = this.input.keyboard.addKeys({
@@ -1348,14 +1368,25 @@ export default class GrassSceneClean extends Phaser.Scene {
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,
});
// Postavi Rain Catcher blizu centra otoka (kamor Kai spawn-a)
const rcX = this.islandX + this.islandWidth / 2 + 200;
const rcY = this.islandY + this.islandHeight / 2;
// 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)
@@ -1373,7 +1404,16 @@ export default class GrassSceneClean extends Phaser.Scene {
// ─── DAY/NIGHT SYSTEM ─────────────────────────────────
this.dayNightSystem = new DayNightSystem(this, {
speed: 1, // 1 sec real = 1 min igre (24 min = 1 dan)
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 ===
@@ -1459,6 +1499,63 @@ export default class GrassSceneClean extends Phaser.Scene {
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) {
@@ -1525,8 +1622,10 @@ export default class GrassSceneClean extends Phaser.Scene {
console.log("SFX: THUD!");
// this.sound.play('tree_thud');
// Visual: Screen Shake
// Visual: Screen Shake (izklopljen v Autism Mode)
if (!this.accessState?.autismMode) {
this.cameras.main.shake(300, 0.005);
}
tree.destroy();
@@ -1880,6 +1979,26 @@ export default class GrassSceneClean extends Phaser.Scene {
/* Removed for cleanup */
update(time, delta) {
if (this.kai && this.kai.active) {
// --- LOOKAHEAD CAMERA OFFSET (Gledanje naprej) ---
const maxOffset = 180; // Koliko pikslov naprej naj pogleda kamera
let targetOffsetX = 0;
let targetOffsetY = 0;
if (this.kai.body.velocity.x > 10) targetOffsetX = maxOffset;
else if (this.kai.body.velocity.x < -10) targetOffsetX = -maxOffset;
if (this.kai.body.velocity.y > 10) targetOffsetY = maxOffset;
else if (this.kai.body.velocity.y < -10) targetOffsetY = -maxOffset;
// Gladko lerpanje proti tarči offseta (manjša vrednost = bolj gladko)
this.cameraLookahead.x = Phaser.Math.Linear(this.cameraLookahead.x, targetOffsetX, 0.03);
this.cameraLookahead.y = Phaser.Math.Linear(this.cameraLookahead.y, targetOffsetY, 0.03);
// V Phaserju pozitiven offset zamakne *tarčo*, kar pomeni da gre kamera v drugo smer!
this.cameras.main.setFollowOffset(this.cameraLookahead.x, this.cameraLookahead.y);
}
// --- ANIMATE OCEAN ---
// (Parallax skipped for rectangles, logic removed to prevent errors)
// --- ANIMATE FOAM ---
@@ -1935,10 +2054,25 @@ export default class GrassSceneClean extends Phaser.Scene {
const velocity = new Phaser.Math.Vector2(0, 0);
// Input helpers
const left = this.cursors.left.isDown || this.keys.left.isDown;
const right = this.cursors.right.isDown || this.keys.right.isDown;
const up = this.cursors.up.isDown || this.keys.up.isDown;
const down = this.cursors.down.isDown || this.keys.down.isDown;
let left = this.cursors.left.isDown || this.keys.left.isDown;
let right = this.cursors.right.isDown || this.keys.right.isDown;
let up = this.cursors.up.isDown || this.keys.up.isDown;
let down = this.cursors.down.isDown || this.keys.down.isDown;
// ── ENOROČNI NAČIN (Sledenje miški z držanjem klika) ──
if (this.accessState?.onehandMode && this.input.activePointer.isDown) {
const ptr = this.input.activePointer;
// Preprečimo tresenje, če je miška preblizu lika
if (Phaser.Math.Distance.Between(this.kai.x, this.kai.y - 30, ptr.worldX, ptr.worldY) > 25) {
const angle = Phaser.Math.Angle.Between(this.kai.x, this.kai.y - 30, ptr.worldX, ptr.worldY);
const dx = Math.cos(angle);
const dy = Math.sin(angle);
if (dx < -0.3) left = true;
if (dx > 0.3) right = true;
if (dy < -0.3) up = true;
if (dy > 0.3) down = true;
}
}
const space = this.cursors.space.isDown;
if (left) velocity.x = -1;

View File

@@ -12,6 +12,7 @@
* - Fog (megla) ki se premika
* ============================================================
*/
import LocalizationSystem from '../systems/LocalizationSystem.js';
export default class MenuScene extends Phaser.Scene {
constructor() {
super({ key: 'MenuScene' });
@@ -20,6 +21,9 @@ export default class MenuScene extends Phaser.Scene {
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');
@@ -38,6 +42,10 @@ export default class MenuScene extends Phaser.Scene {
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
// ─────────────────────────────────────────────────────────────────
@@ -165,9 +173,10 @@ export default class MenuScene extends Phaser.Scene {
const BTN_GAP = 95;
const buttons = [
{ label: '▶ IGRAJ', id: 'play', color: '#44ffcc', hColor: '#ffffff' },
{ label: '⚙ NASTAVITVE', id: 'settings', color: '#aabbcc', hColor: '#ffffff' },
{ label: '✕ IZHOD', id: 'quit', color: '#cc6666', hColor: '#ff8888' },
{ 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 = [];
@@ -188,7 +197,7 @@ export default class MenuScene extends Phaser.Scene {
// Gumb tekst
const txt = this.add.text(BTN_X, y, b.label, {
fontFamily: '"Arial Black", Arial',
fontFamily: this.i18n.fontFamily,
fontSize: '26px',
color: b.color,
stroke: '#000000',
@@ -284,6 +293,15 @@ export default class MenuScene extends Phaser.Scene {
// 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) {

View File

@@ -14,6 +14,9 @@
* OVERLAY: Health Critical pulse (pri < 25% HP)
* =============================================================
*/
import LocalizationSystem from '../systems/LocalizationSystem.js';
import AccessibilityManager from '../systems/AccessibilityManager.js';
export default class UIScene extends Phaser.Scene {
constructor() {
super({ key: 'UIScene', active: false }); // Zaženemo ročno iz GrassScene
@@ -31,6 +34,11 @@ export default class UIScene extends Phaser.Scene {
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
}
};
}
@@ -56,6 +64,10 @@ export default class UIScene extends Phaser.Scene {
const H = this.cameras.main.height; // 1080
const PAD = 24;
// ─── INITIALIZE LOCALIZATION ───
this.i18n = new LocalizationSystem(this);
this.i18n.init();
// ─────────────────────────────────────────────────────────────────
// TOP LEFT — Trial badge + HP + Energy
// ─────────────────────────────────────────────────────────────────
@@ -76,6 +88,14 @@ export default class UIScene extends Phaser.Scene {
ease: 'Sine.easeInOut',
});
// ── NAKUP IGRE LOGIKA ──
this.trialBadge.setInteractive({ useHandCursor: true });
this.trialBadge.on('pointerdown', () => {
if (!this.gameState.isFullGame) {
this.unlockFullGame();
}
});
// ── HP Bar (health_bar.png je vertikalni triptih: full/empty/empty) ─
// Spritesheet je 3 vrstice: top=HP full, mid=HP empty, bot=Energy
// Uporabljamo health_bar.png kot ozadje in rišemo fill čez njega
@@ -135,6 +155,23 @@ export default class UIScene extends Phaser.Scene {
// 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)
// ─────────────────────────────────────────────────────────────────
@@ -153,8 +190,8 @@ export default class UIScene extends Phaser.Scene {
}).setOrigin(0.5, 0.5).setDepth(1005);
// Dan label (pod uro)
this.dayText = this.add.text(W - PAD - 135, PAD + 10, 'DAN 1', {
fontFamily: 'Arial Black',
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',
@@ -217,8 +254,8 @@ export default class UIScene extends Phaser.Scene {
this.actionBtn.on('pointerout', () => this.actionBtn.clearTint());
// Crafting label
this.add.text(this.actionBtn.x, H - PAD + 2, 'CRAFTING', {
fontFamily: 'Arial',
this.add.text(this.actionBtn.x, H - PAD + 2, this.i18n.t('crafting'), {
fontFamily: this.i18n.fontFamily,
fontSize: '13px',
color: '#aaaaaa',
stroke: '#000',
@@ -254,8 +291,8 @@ export default class UIScene extends Phaser.Scene {
this._mmR = MM_SIZE / 2 - 16;
// "MAP" label
this.add.text(mmCx, mmCy + MM_SIZE / 2 - 8, 'MAPA', {
fontFamily: 'Arial',
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);
@@ -643,4 +680,96 @@ export default class UIScene extends Phaser.Scene {
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

@@ -36,9 +36,8 @@ export default class DayNightSystem {
this.hour = 6; // Začne ob 6:00
this.minute = 0;
// Hitrost: 1 real sekunda = N minut v igri
// Default: 24 real minut = 1 dan → 1 real sec = 1 igra minuta
this.gameMinutesPerRealSecond = options.speed || 1;
// 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 }
@@ -124,6 +123,11 @@ export default class DayNightSystem {
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,
@@ -226,6 +230,63 @@ export default class DayNightSystem {
});
}
// ─────────────────────────────────────────────────────────────────────────
// 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
// ─────────────────────────────────────────────────────────────────────────
@@ -256,14 +317,14 @@ export default class DayNightSystem {
alpha = 0.35 + t * 0.25;
r = 0x00; g = 0x00; b = 0x44;
} else if (h >= 22 || h < 4) {
// Noč: globoka modra tema
alpha = 0.60;
r = 0x00; g = 0x00; b = 0x22;
// 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.60 * (1 - t * 0.5);
r = 0x00; g = 0x00; b = 0x22;
alpha = 0.85 * (1 - t * 0.5);
r = 0x01; g = 0x01; b = 0x08;
}
this.ambientOverlay.clear();

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

@@ -63,6 +63,11 @@ export default class TwinBondSystem {
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;
@@ -76,7 +81,9 @@ export default class TwinBondSystem {
const W = this.scene.cameras.main.width;
const H = this.scene.cameras.main.height;
this.flashOverlay.clear();
this.flashOverlay.fillStyle(0x3a0055, 0.85); // Temno vijolična
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

View File

@@ -99,9 +99,10 @@ export default class ZombieSystem {
this._nightCount++;
// Stopnjevanje tezje po vsaki noci
this.maxZombies = Math.min(2 + this._nightCount, 8);
const spawnDelay = Math.max(6000, 14000 - this._nightCount * 1000);
this.zombieSpeed = Math.min(35 + this._nightCount * 3, 65);
// 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`);
@@ -112,6 +113,10 @@ export default class ZombieSystem {
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();
}
}
});
@@ -205,6 +210,49 @@ export default class ZombieSystem {
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()
// ─────────────────────────────────────────────────────────────────────────
@@ -287,10 +335,10 @@ export default class ZombieSystem {
moveX = Math.cos(angle) * speed * dt;
moveY = Math.sin(angle) * speed * dt;
// Rdeči halo (nevarnost)
// Rdeči halo (nevarnost) ali oranžen za rakune
z.halo.clear();
z.halo.fillStyle(0xff0000, 0.18);
z.halo.fillCircle(z.sprite.x, z.sprite.y - 20, 40);
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 ===