diff --git a/assets/.DS_Store b/assets/.DS_Store index 57f62c7ae..b84bb15b7 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/DEMO_FAZA1/.DS_Store b/assets/DEMO_FAZA1/.DS_Store index 40506e2d0..f5e70d271 100644 Binary files a/assets/DEMO_FAZA1/.DS_Store and b/assets/DEMO_FAZA1/.DS_Store differ diff --git a/nova farma TRAE/assets/DEMO_FAZA1/Buildings/chest_closed.png b/nova farma TRAE/assets/DEMO_FAZA1/Buildings/chest_closed.png new file mode 100644 index 000000000..92cecebc4 Binary files /dev/null and b/nova farma TRAE/assets/DEMO_FAZA1/Buildings/chest_closed.png differ diff --git a/nova farma TRAE/assets/DEMO_FAZA1/Buildings/wooden_fence.png b/nova farma TRAE/assets/DEMO_FAZA1/Buildings/wooden_fence.png new file mode 100644 index 000000000..d0be70836 Binary files /dev/null and b/nova farma TRAE/assets/DEMO_FAZA1/Buildings/wooden_fence.png differ diff --git a/nova farma TRAE/remove_bg.py b/nova farma TRAE/remove_bg.py new file mode 100644 index 000000000..e68c5f3b5 --- /dev/null +++ b/nova farma TRAE/remove_bg.py @@ -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 ") + else: + process_image(sys.argv[1], sys.argv[2]) diff --git a/nova farma TRAE/src/scenes/GrassScene_Clean.js b/nova farma TRAE/src/scenes/GrassScene_Clean.js index 9de40e470..39fb50f13 100644 --- a/nova farma TRAE/src/scenes/GrassScene_Clean.js +++ b/nova farma TRAE/src/scenes/GrassScene_Clean.js @@ -4,6 +4,8 @@ import WaterSystem from '../systems/WaterSystem.js'; import DayNightSystem from '../systems/DayNightSystem.js'; import InventorySystem from '../systems/InventorySystem.js'; import ZombieSystem from '../systems/ZombieSystem.js'; +import TwinBondSystem from '../systems/TwinBondSystem.js'; +import JournalSystem from '../systems/JournalSystem.js'; export default class GrassSceneClean extends Phaser.Scene { constructor() { @@ -117,6 +119,7 @@ export default class GrassSceneClean extends Phaser.Scene { this.load.image('campfire', 'DEMO_FAZA1/Environment/taborni_ogenj.png'); this.load.image('tent', 'DEMO_FAZA1/Environment/sotor.png'); this.load.image('sleeping_bag', 'DEMO_FAZA1/Items/spalna_vreca.png'); + this.load.image('chest_closed', 'DEMO_FAZA1/Buildings/chest_closed.png'); // 7. NEW: Gronk & Structures this.load.spritesheet('gronk', 'DEMO_FAZA1/Characters/gronk_walk_sheet.png', { @@ -137,6 +140,9 @@ export default class GrassSceneClean extends Phaser.Scene { // 11. Zombie System Assets ZombieSystem.preload(this); + + // 12. Building System Assets + this.load.image('wooden_fence', 'DEMO_FAZA1/Buildings/wooden_fence.png'); } // =================================================================== @@ -202,6 +208,35 @@ export default class GrassSceneClean extends Phaser.Scene { // 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; } @@ -357,17 +392,13 @@ export default class GrassSceneClean extends Phaser.Scene { // FAILSAFE 2: Use simple Rectangles instead of TileSprites/Textures to STOP OOM. // We will add waves later if stability allows. - const viewW = this.scale.width || 1280; - const viewH = this.scale.height || 720; - - this.oceanLayer1 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x004488) - .setScrollFactor(0) + // Ustvarimo dva velika modra pravokotnika kot 'bazen', v katerem je otok + this.oceanLayer1 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x004488) .setDepth(-290) .setAlpha(0.5); // Optional: Second layer for depth effect - this.oceanLayer2 = this.add.rectangle(viewW / 2, viewH / 2, viewW + 100, viewH + 100, 0x002244) - .setScrollFactor(0) + this.oceanLayer2 = this.add.rectangle(WORLD_W / 2, WORLD_H / 2, WORLD_W + 2000, WORLD_H + 2000, 0x002244) .setDepth(-295) // Behind .setAlpha(1.0); @@ -960,34 +991,95 @@ export default class GrassSceneClean extends Phaser.Scene { this.physics.add.overlap(this.kai, this.sleepingBag, () => { if (this.lastCheckpointTime && this.time.now - this.lastCheckpointTime < 5000) return; - this.saveCheckpoint(this.sleepingBag.x, this.sleepingBag.y); - this.lastCheckpointTime = this.time.now; }); */ - console.log('SPALNA VREČA ODSTRANJENA'); + // ─── 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); - // =================================================================== - // TEST: AUTO-LOADER DEMO - // =================================================================== - // Place test vegetation and tree to verify grounding system + this.starterChest = this.physics.add.image(SPAWN_X - 100, 5600, 'chest_closed'); + this.starterChest.setOrigin(0.5, 1.0); + this.starterChest.setScale(0.18); + this.starterChest.setDepth(5600); + this.starterChest.body.setSize(this.starterChest.width * 0.18, this.starterChest.height * 0.18); + this.starterChest.body.setImmovable(true); + this.starterChest.opened = false; - const testTileBottom = SPAWN_Y + 200; // Tile bottom Y position + 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); + chestText.setDepth(9999); - // TEST 1: Vegetation (28px height, 4px burial) - this.testGrass = this.loadVegetation( - SPAWN_X - 100, // Left of Kai - testTileBottom, - 'grass_ref_1' - ); - console.log('🌿 TEST: Grass loaded at 28px with 4px burial'); + 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'); - // TEST 2: Tree (160px height, 8px burial) - this.testTree = this.loadTree( - SPAWN_X + 100, // Right of Kai - testTileBottom, - 'tree_adult_0' - ); - console.log('🌳 TEST: Oak loaded at 160px with 8px burial'); + 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'); + } + }); + // ───────────────────────────────────────────────────────────────── + // === RANDOM TREE GENERATION (AUTO-LOADER) === + 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; + } + + let tKey = Phaser.Utils.Array.GetRandom(treeKeys); + let t = this.loadTree(tx, ty, tKey); + t.setMask(this.islandMask); + this.treesGroup.add(t); + } + + // Add solid collision against trees! + this.physics.add.collider(this.kai, this.treesGroup); // =================================================================== // Adjust Physics Body for larger size @@ -1105,6 +1197,9 @@ export default class GrassSceneClean extends Phaser.Scene { 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'); @@ -1309,11 +1404,8 @@ export default class GrassSceneClean extends Phaser.Scene { } }); - // Registriraj spalno vrečo kot sleep objekt - // (spawn point je center otoka) - const sleepX = this.islandX + this.islandWidth / 2; - const sleepY = this.islandY + this.islandHeight / 2; - this.dayNightSystem.registerSleepObject(sleepX, sleepY, null, 'sleeping_bag'); + // 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!'); @@ -1346,6 +1438,10 @@ export default class GrassSceneClean extends Phaser.Scene { }); 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(); @@ -1434,6 +1530,42 @@ export default class GrassSceneClean extends Phaser.Scene { tree.destroy(); + // Odkleni dnevniski vnos ob prvem podrtju drevesa + if (this.journalSystem && !this._firstTreeFelled) { + this._firstTreeFelled = true; + this.time.delayedCall(800, () => { + this.journalSystem.unlockEntry('first_tree'); + }); + } + + // Drop wood visual (3 pieces) + for (let i = 0; i < 3; i++) { + let wx = tx + (Math.random() - 0.5) * 100; + let wy = ty - 50 + (Math.random() - 0.5) * 50; + let w = this.add.text(wx, wy, '🪵', { fontSize: '35px' }).setOrigin(0.5); + w.setDepth(wy); // y-sorting + this.physics.add.existing(w); + w.body.setSize(40, 40); + + // Simple jump/pop animation for the drop + this.tweens.add({ + targets: w, + y: wy - 40, + duration: 200, + yoyo: true, + ease: 'Quad.easeOut' + }); + + // Add collision to collect it + this.physics.add.overlap(this.kai, w, () => { + w.destroy(); + if (this.inventorySystem) { + this.inventorySystem.addItem('wood', 1); + this.inventorySystem._showMsg('+1 Les', '#ddaa00'); + } + }); + } + // 3. AUTO-REPLANT (Spawn Grass Clump) this.spawnReplant(tx, ty); // Use generic regrowth for consistency if desired, or keep as specialized spawn } @@ -1795,35 +1927,40 @@ export default class GrassSceneClean extends Phaser.Scene { }); // --- PLAYER MOVEMENT (SMOOTHENED) --- - const speed = 500; - const velocity = new Phaser.Math.Vector2(0, 0); - - // Input helpers - const left = this.cursors.left.isDown || this.keys.left.isDown; - const right = this.cursors.right.isDown || this.keys.right.isDown; - const up = this.cursors.up.isDown || this.keys.up.isDown; - const down = this.cursors.down.isDown || this.keys.down.isDown; - const space = this.cursors.space.isDown; - - if (left) velocity.x = -1; - else if (right) velocity.x = 1; - - if (up) velocity.y = -1; - else if (down) velocity.y = 1; - - if (velocity.length() > 0) { - velocity.normalize().scale(speed); - this.kai.setVelocity(velocity.x, velocity.y); - - // Animation logic - if (Math.abs(velocity.x) > Math.abs(velocity.y)) { - this.kai.play(velocity.x < 0 ? 'walk-left' : 'walk-right', true); - } else { - this.kai.play(velocity.y < 0 ? 'walk-up' : 'walk-down', true); - } - } else { + if (this.kai.canMove === false) { this.kai.setVelocity(0, 0); this.kai.anims.stop(); + } else { + const speed = 500; + const velocity = new Phaser.Math.Vector2(0, 0); + + // Input helpers + const left = this.cursors.left.isDown || this.keys.left.isDown; + const right = this.cursors.right.isDown || this.keys.right.isDown; + const up = this.cursors.up.isDown || this.keys.up.isDown; + const down = this.cursors.down.isDown || this.keys.down.isDown; + const space = this.cursors.space.isDown; + + if (left) velocity.x = -1; + else if (right) velocity.x = 1; + + if (up) velocity.y = -1; + else if (down) velocity.y = 1; + + if (velocity.length() > 0) { + velocity.normalize().scale(speed); + this.kai.setVelocity(velocity.x, velocity.y); + + // Animation logic + if (Math.abs(velocity.x) > Math.abs(velocity.y)) { + this.kai.play(velocity.x < 0 ? 'walk-left' : 'walk-right', true); + } else { + this.kai.play(velocity.y < 0 ? 'walk-up' : 'walk-down', true); + } + } else { + this.kai.setVelocity(0, 0); + this.kai.anims.stop(); + } } @@ -1877,6 +2014,11 @@ export default class GrassSceneClean extends Phaser.Scene { 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); diff --git a/nova farma TRAE/src/systems/BuildingSystem.js b/nova farma TRAE/src/systems/BuildingSystem.js index a60b99b58..c6ea7b38b 100644 --- a/nova farma TRAE/src/systems/BuildingSystem.js +++ b/nova farma TRAE/src/systems/BuildingSystem.js @@ -30,6 +30,16 @@ export default class BuildingSystem { // 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', @@ -48,24 +58,6 @@ export default class BuildingSystem { colliderOffsetX: -25, colliderOffsetY: -20, }, - { - key: 'rain_catcher', - label: 'Zbirač dežja', - scale: 2.0, - colliderW: 140, - colliderH: 40, - colliderOffsetX: -70, - colliderOffsetY: -40, - }, - { - key: 'foundation_concrete', - label: 'Betonski temelj', - scale: 2.0, - colliderW: 120, - colliderH: 20, - colliderOffsetX: -60, - colliderOffsetY: -20, - }, ]; this._setupInput(); @@ -99,7 +91,7 @@ export default class BuildingSystem { if (!this._modeTip) { this._modeTip = this.scene.add.text( this.scene.cameras.main.width / 2, 20, - '🏗️ NAČIN GRADNJE | Klik = postavi | B / Esc = izhod', + '🏗️ NAČIN GRADNJE | 🖱️ postavi | B / Esc izhod | R rotacija', { fontSize: '16px', color: '#44ffaa', @@ -164,12 +156,30 @@ export default class BuildingSystem { const wx = pointer.worldX; const wy = pointer.worldY; - this.ghost.setPosition(wx, wy); - // Zelena = na otoku, Rdeča = zunaj + // 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; - const ok = wx > b.x && wx < b.x + b.width && - wy > b.y && wy < b.y + b.height; + 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); } @@ -177,28 +187,31 @@ export default class BuildingSystem { // PLACE BUILDING (ob kliku) // ========================================== placeBuilding(worldX, worldY) { - if (!this.active || !this.selectedKey) return; + 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 (worldX <= b.x || worldX >= b.x + b.width || - worldY <= b.y || worldY >= b.y + b.height) { - // Flash rdeče - if (this.ghost) { - this.scene.tweens.add({ - targets: this.ghost, - alpha: { from: 0.9, to: 0.2 }, - duration: 80, - yoyo: true, - repeat: 2, - }); - } + 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; @@ -207,18 +220,18 @@ export default class BuildingSystem { const scale = targetH / srcH; // Ustvari static physics image - const building = this.scene.physics.add.staticImage(worldX, worldY, this.selectedKey) + const building = this.scene.physics.add.staticImage(finalX, finalY, this.selectedKey) .setScale(scale) .setOrigin(0.5, 1.0) - .setDepth(worldY); + .setDepth(finalY); // Physics collider body (samo baza stavbe) building.body.setSize(def.colliderW, def.colliderH); building.body.setOffset( - (texture.getSourceImage().width * scale) / 2 + def.colliderOffsetX, - srcH * scale + def.colliderOffsetY + def.colliderOffsetX || 0, + def.colliderOffsetY || 0 ); - building.body.reset(worldX, worldY); + building.body.reset(finalX, finalY); // Collider med Kai in stavbo this.buildingsGroup.add(building, true); @@ -234,7 +247,7 @@ export default class BuildingSystem { }); // Flash feedback - const flash = this.scene.add.rectangle(worldX, worldY - targetH * 0.5, 80, 80, 0x44ffaa, 0.6) + const flash = this.scene.add.rectangle(finalX, finalY - targetH * 0.5, 80, 80, 0x44ffaa, 0.6) .setDepth(99998); this.scene.tweens.add({ targets: flash, @@ -243,12 +256,20 @@ export default class BuildingSystem { onComplete: () => flash.destroy(), }); - // Shrani referenco za Y-sort v update() - this.scene._buildingSprites = this.scene._buildingSprites || []; - this.scene._buildingSprites.push(building); + this.placed.push({ key: this.selectedKey, x: finalX, y: finalY }); - this.placed.push({ key: this.selectedKey, x: worldX, y: worldY }); + console.log(`🏗️ Placed: ${this.selectedKey} @ (${Math.round(finalX)}, ${Math.round(finalY)})`); + } - console.log(`🏗️ Placed: ${this.selectedKey} @ (${Math.round(worldX)}, ${Math.round(worldY)})`); + _flashGhost() { + if (this.ghost) { + this.scene.tweens.add({ + targets: this.ghost, + alpha: { from: 0.9, to: 0.2 }, + duration: 80, + yoyo: true, + repeat: 2, + }); + } } } diff --git a/nova farma TRAE/src/systems/DayNightSystem.js b/nova farma TRAE/src/systems/DayNightSystem.js index 0cbc2dea1..0502d4306 100644 --- a/nova farma TRAE/src/systems/DayNightSystem.js +++ b/nova farma TRAE/src/systems/DayNightSystem.js @@ -48,9 +48,8 @@ export default class DayNightSystem { // Input [Z] za spanje this.keyZ = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z); - // Ambient Graphics (overlay za noč) + // Ambient Graphics (overlay za noč) - Pokriva celoten svet! this.ambientOverlay = scene.add.graphics() - .setScrollFactor(0) .setDepth(4500); // Pod UIScene (depth 5000+), nad svetom // Star particles za noč @@ -273,7 +272,10 @@ export default class DayNightSystem { (r << 16) | (g << 8) | b, alpha ); - this.ambientOverlay.fillRect(0, 0, W, H); + // 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); } } @@ -282,15 +284,14 @@ export default class DayNightSystem { // ───────────────────────────────────────────────────────────────────────── _initStars() { const W = this.scene.cameras.main.width; - const H = this.scene.cameras.main.height; this.starGfx = this.scene.add.graphics() - .setScrollFactor(0) .setDepth(4499); - for (let i = 0; i < 80; i++) { + // Zvezde raztrosimo čez celoten svet (zgoraj) + for (let i = 0; i < 400; i++) { this.stars.push({ - x: Math.random() * W, - y: Math.random() * H * 0.6, // Samo zgoraj + 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, diff --git a/nova farma TRAE/src/systems/FarmingSystem.js b/nova farma TRAE/src/systems/FarmingSystem.js index ea81110ba..ef7eba283 100644 --- a/nova farma TRAE/src/systems/FarmingSystem.js +++ b/nova farma TRAE/src/systems/FarmingSystem.js @@ -323,6 +323,12 @@ export default class FarmingSystem { 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'); + } } // ───────────────────────────────────────────────────────────────────────── @@ -421,6 +427,14 @@ export default class FarmingSystem { }); 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'); + }); + } } // ───────────────────────────────────────────────────────────────────────── diff --git a/nova farma TRAE/src/systems/InventorySystem.js b/nova farma TRAE/src/systems/InventorySystem.js index 2f4b56d86..9881c5fe3 100644 --- a/nova farma TRAE/src/systems/InventorySystem.js +++ b/nova farma TRAE/src/systems/InventorySystem.js @@ -29,17 +29,17 @@ export default class InventorySystem { this.items = {}; // Format: { 'wheat_seed': { name, icon, count, type, tool/seed } } - // Hotbar = 7 slotov, vsak kaže na item key ali null + // Hotbar = 7 slotov, vsak kaže na item key ali null (začenjamo prazni!) this.hotbar = [ - 'hoe', - 'watering_can', - 'wheat_seed', - 'carrot_seed', + null, + null, + null, + null, null, null, null, ]; - this.activeSlot = 0; // Slot 1 = Motika + this.activeSlot = 0; // Slot 1 // Item definicije this.ITEM_DEFS = { @@ -61,6 +61,15 @@ export default class InventorySystem { 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: '🌾', @@ -88,8 +97,9 @@ export default class InventorySystem { seed: 'cannabis', stackable: true, count: 0, - desc: '7 dni • 200g/harvest', + desc: '[E] Posadi na preorano', color: '#88cc44', + value: 50, }, // Harvested items wheat: { @@ -116,6 +126,14 @@ export default class InventorySystem { 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) @@ -169,9 +187,9 @@ export default class InventorySystem { } this.items[key].count += count; - // Dodaj v hotbar če je prazen slot + seme/resource + // Dodaj v hotbar če je prazen slot in item še ni v hotbaru const def = this.ITEM_DEFS[key]; - if (def && def.stackable && !this.hotbar.includes(key)) { + if (def && !this.hotbar.includes(key)) { const emptySlot = this.hotbar.findIndex(s => s === null); if (emptySlot !== -1) this.hotbar[emptySlot] = key; } @@ -186,6 +204,23 @@ export default class InventorySystem { 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) // ───────────────────────────────────────────────────────────────────────── diff --git a/nova farma TRAE/src/systems/JournalSystem.js b/nova farma TRAE/src/systems/JournalSystem.js new file mode 100644 index 000000000..a4fefa417 --- /dev/null +++ b/nova farma TRAE/src/systems/JournalSystem.js @@ -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'); + } + } +} diff --git a/nova farma TRAE/src/systems/TwinBondSystem.js b/nova farma TRAE/src/systems/TwinBondSystem.js new file mode 100644 index 000000000..f8d77c0c3 --- /dev/null +++ b/nova farma TRAE/src/systems/TwinBondSystem.js @@ -0,0 +1,137 @@ +/** + * 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; + + // 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(); + this.flashOverlay.fillStyle(0x3a0055, 0.85); // 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); + } +} diff --git a/nova farma TRAE/src/systems/ZombieSystem.js b/nova farma TRAE/src/systems/ZombieSystem.js index 0c8825704..51ebc6a5a 100644 --- a/nova farma TRAE/src/systems/ZombieSystem.js +++ b/nova farma TRAE/src/systems/ZombieSystem.js @@ -27,12 +27,13 @@ export default class ZombieSystem { this.islandH = options.islandH || 6400; // Zombi parametri - this.maxZombies = 3; + 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.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 = []; @@ -94,8 +95,18 @@ export default class ZombieSystem { // ───────────────────────────────────────────────────────────────────────── startNightSpawning() { if (this.spawnTimer) return; + + 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); + + console.log(`🌙 Noč ${this._nightCount}: max=${this.maxZombies}, delay=${spawnDelay}ms, speed=${this.zombieSpeed}px/s`); + this.spawnTimer = this.scene.time.addEvent({ - delay: 12000, // Vsakih 12s poskusi spawnat (ponoči) + delay: spawnDelay, loop: true, callback: () => { if (this.zombies.length < this.maxZombies) { @@ -103,10 +114,19 @@ export default class ZombieSystem { } } }); + // 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!'); }