diff --git a/.DS_Store b/.DS_Store index 6adc762c1..6c2455fe7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store index 81d638fae..f9cc81b16 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 27461c8cc..93c2f3690 100644 Binary files a/assets/DEMO_FAZA1/.DS_Store and b/assets/DEMO_FAZA1/.DS_Store differ diff --git a/assets/DEMO_FAZA1/Environment/.DS_Store b/assets/DEMO_FAZA1/Environment/.DS_Store deleted file mode 100644 index 5008ddfcf..000000000 Binary files a/assets/DEMO_FAZA1/Environment/.DS_Store and /dev/null differ diff --git a/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png b/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png new file mode 100644 index 000000000..8a3f44238 Binary files /dev/null and b/assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..cd644a236 --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + + Nova Farma - Clean Start + + + + + + + + + + + diff --git a/main.js b/main.js new file mode 100644 index 000000000..80d5150d4 --- /dev/null +++ b/main.js @@ -0,0 +1,48 @@ +const { app, BrowserWindow, ipcMain } = require('electron'); +const path = require('path'); +const fs = require('fs'); + +let mainWindow; + +function createWindow() { + mainWindow = new BrowserWindow({ + width: 1280, + height: 720, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + }, + backgroundColor: '#000000', + title: 'Mrtva Dolina - Death Valley' + }); + + mainWindow.loadFile('index.html'); + mainWindow.webContents.openDevTools(); + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +ipcMain.on('log-action', (event, message) => { + console.log('[LOG]', message); +}); + +app.whenReady().then(() => { + createWindow(); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); + + // Handle security warnings + process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); diff --git a/scripts/clean_pink_grass.py b/scripts/clean_pink_grass.py new file mode 100644 index 000000000..43d09d8f8 --- /dev/null +++ b/scripts/clean_pink_grass.py @@ -0,0 +1,59 @@ +import cv2 +import numpy as np +import os + +def remove_pink_bg(): + input_path = '/Users/davidkotnik/.gemini/antigravity/brain/63340bd3-91a9-439d-b1d9-5692ce5adaea/visoka_trava_v2_pink_bg_1769436757738.png' + output_path = 'assets/DEMO_FAZA1/Vegetation/visoka_trava_v2.png' + + # Ensure directory exists + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + # Read image + img = cv2.imread(input_path) + if img is None: + print(f"Error: Could not read {input_path}") + return + + # Convert to RGBA + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Define Magenta range (B, G, R) - standard magenta is (255, 0, 255) in BGR + # We'll allow a small tolerance to catch anti-aliasing edges + sensitivity = 20 + lower_magenta = np.array([255 - sensitivity, 0, 255 - sensitivity]) + upper_magenta = np.array([255, sensitivity, 255]) + + # Create mask + # Note: OpenCV reads as BGR, so Pink #FF00FF is (255, 0, 255) + # Using simple color thresholding + mask = cv2.inRange(img[:,:,:3], lower_magenta, upper_magenta) + + # Invert mask (we want to keep non-magenta) + mask_inv = cv2.bitwise_not(mask) + + # Apply mask to alpha channel + # Where mask is white (magenta pixels), alpha becomes 0 + # But mask_inv is white for KEEP pixels. + # So we want Alpha to be 0 where mask is 255. + + # Better approach: + # 1. Split channels + b, g, r, a = cv2.split(img) + + # 2. Update alpha channel using the inverted mask + # If mask pixel is 255 (magenta), mask_inv is 0. We want alpha 0 there. + # If mask pixel is 0 (not magenta), mask_inv is 255. We want alpha 255 there. + # However, original alpha is 255 everywhere inside the image bounds. + # So we can just take bitwise_and or just set alpha to mask_inv. + + img[:, :, 3] = mask_inv + + # Optional: Basic edge smoothing/despiking if needed, but for pixel/vector art simple cut is often better. + + # Save + cv2.imwrite(output_path, img) + print(f"Successfully saved transparent image to {output_path}") + +if __name__ == "__main__": + remove_pink_bg() diff --git a/scripts/process_hay.py b/scripts/process_hay.py new file mode 100644 index 000000000..af0cc95db --- /dev/null +++ b/scripts/process_hay.py @@ -0,0 +1,71 @@ +import cv2 +import numpy as np +import os + +def process_hay(): + # Paths + input_path = 'assets/references/proizvodnja_sena.png' + output_dir = 'assets/DEMO_FAZA1/Items/Hay' + + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Load image + img = cv2.imread(input_path) + if img is None: + print(f"Error: Could not load {input_path}") + return + + # Convert to RGBA + img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) + + # Define Green Key (assuming bright green background) + # Range for Chroma Key Green in HSV is best, but BGR is fine for simple flat green + # Pure Green in BGR is (0, 255, 0) + + # Using HSV for better masking + hsv = cv2.cvtColor(img[:,:,:3], cv2.COLOR_BGR2HSV) + + # Mask for Green (Hue 35-85 approx for standard green screen) + lower_green = np.array([35, 50, 50]) + upper_green = np.array([85, 255, 255]) + + mask = cv2.inRange(hsv, lower_green, upper_green) + + # Invert mask (we want non-green parts) + mask_inv = cv2.bitwise_not(mask) + + # Set Alpha channel based on mask + # Everything green becomes transparent + img[:, :, 3] = mask_inv + + # Find contours of objects + # Use the mask_inv (where 255 is the object) + contours, _ = cv2.findContours(mask_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + print(f"Found {len(contours)} objects.") + + count = 0 + for cnt in contours: + x, y, w, h = cv2.boundingRect(cnt) + + # Filter noise + if w < 10 or h < 10: + continue + + # Crop + crop = img[y:y+h, x:x+w] + + # Determine if it's a small pile or big bale based on size + # This is a heuristic. We'll label them by size. + area = w * h + label = "hay_piece" + + # Save + output_filename = f"{output_dir}/hay_drop_{count}.png" + cv2.imwrite(output_filename, crop) + print(f"Saved {output_filename} (Size: {w}x{h})") + count += 1 + +if __name__ == "__main__": + process_hay() diff --git a/src/game.js b/src/game.js index c404c055a..d3a18422f 100644 --- a/src/game.js +++ b/src/game.js @@ -6,6 +6,13 @@ const config = { height: window.innerHeight, backgroundColor: '#1a1a1a', // Temno siva, da slika izstopa parent: 'body', + physics: { + default: 'arcade', + arcade: { + debug: false, + gravity: { y: 0 } + } + }, scene: [GrassScene] }; diff --git a/src/scenes/GrassScene_Clean.js b/src/scenes/GrassScene_Clean.js index 77b4890c3..745f0d83d 100644 --- a/src/scenes/GrassScene_Clean.js +++ b/src/scenes/GrassScene_Clean.js @@ -1,35 +1,29 @@ export default class GrassScene extends Phaser.Scene { constructor() { super({ key: 'GrassScene' }); - this.baseTime = 12; // Start at 12:00 + this.baseTime = 12; this.timeSpeed = 0.5; + this.playerSpeed = 200; + this.hayCount = 0; } preload() { console.log("🌿 Loading Clean Assets..."); - // 1. PATHS (Absolute from server root) const PATHS = { - ground: '/assets/DEMO_FAZA1/Ground/', - veg: '/assets/DEMO_FAZA1/Vegetation/' + ground: 'assets/DEMO_FAZA1/Ground/', + veg: 'assets/DEMO_FAZA1/Vegetation/', + char: 'assets/references/', + items: 'assets/DEMO_FAZA1/Items/Hay/', + audio: 'assets/audio/_NEW/' }; - // 2. LOAD ASSETS - // Ground this.load.image('ground_base', PATHS.ground + 'tla_trava_tekstura.png'); - - // Vegetation - Grass - this.load.image('grass_tuft', PATHS.veg + 'trava_sop.png'); this.load.image('grass_tall', PATHS.veg + 'visoka_trava.png'); + this.load.image('hay_drop', PATHS.items + 'hay_drop_0.png'); + this.load.image('kai', PATHS.char + 'kaj.png'); + this.load.audio('step_grass', PATHS.audio + 'footstep_grass_000.ogg'); - // Vegetation - Trees (All phases) - this.load.image('tree_f1', PATHS.veg + 'drevo_faza_1.png'); // Kalcek - this.load.image('tree_f2', PATHS.veg + 'drevo_faza_2.png'); // Mlado - this.load.image('tree_small', PATHS.veg + 'drevo_majhno.png'); - this.load.image('tree_medium', PATHS.veg + 'drevo_srednje.png'); - this.load.image('tree_large', PATHS.veg + 'drevo_veliko.png'); // Hero - - // Error handling this.load.on('loaderror', (file) => { console.error('FAILED TO LOAD:', file.src); }); @@ -38,127 +32,185 @@ export default class GrassScene extends Phaser.Scene { create() { const { width, height } = this.scale; - // 1. BACKGROUND (Tiling Sprite - Base Layer) + // 1. BACKGROUND this.ground = this.add.tileSprite(0, 0, width, height, 'ground_base') .setOrigin(0, 0); - // 2. VEGETATION GROUPS - this.grasses = []; - this.trees = []; + // 2. GROUPS + this.tallGrassGroup = this.physics.add.group(); + this.hayGroup = this.physics.add.group(); - // 3. GENERATE WORLD - this.generateGrass(width, height); - this.generateTrees(width, height); + this.allGrass = []; - // 4. DAY/NIGHT OVERLAY + // 3. GENERATE WORLD - ONLY GRASS + // "Vsa drevesa odstranjena. Samo trava ostane." + // "Nastavi neskončno travo čez celo mapo" + this.generateInfiniteGrass(width, height); + + // 4. KAI (Player) + this.player = this.physics.add.sprite(width / 2, height / 2, 'kai'); + this.player.setScale(0.5); + this.player.setOrigin(0.5, 0.92); + this.player.setCollideWorldBounds(true); + this.player.setDepth(this.player.y); + + this.lastStepTime = 0; + + // 5. INTERACTION LOGIC + this.physics.add.overlap(this.player, this.tallGrassGroup, this.handleMow, null, this); + this.physics.add.overlap(this.player, this.hayGroup, this.collectHay, null, this); + + // Input + this.cursors = this.input.keyboard.createCursorKeys(); + this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + + // 6. AMNESIA FADE IN + this.cameras.main.fadeIn(3000, 0, 0, 0); + + // DYNAMIC VIGNETTE + const canvasTexture = this.textures.createCanvas('vignette_tex', width, height); + const ctx = canvasTexture.context; + const grd = ctx.createRadialGradient(width / 2, height / 2, height * 0.3, width / 2, height / 2, height * 0.8); + grd.addColorStop(0, "rgba(0,0,0,0)"); + grd.addColorStop(1, "rgba(0,0,0,0.9)"); + + ctx.fillStyle = grd; + ctx.fillRect(0, 0, width, height); + canvasTexture.refresh(); + + this.vignette = this.add.image(width / 2, height / 2, 'vignette_tex') + .setScrollFactor(0) + .setAlpha(0) + .setDepth(8000); + + // 7. DAY/NIGHT OVERLAY this.dayNightOverlay = this.add.rectangle(0, 0, width, height, 0x000000, 0) .setOrigin(0, 0) - .setDepth(9000); // Top layer + .setDepth(9000); - // 5. UI INFO - this.infoText = this.add.text(20, 20, 'Time: 12:00', { - fontFamily: 'monospace', fontSize: '24px', fill: '#ffffff', stroke: '#000000', strokeThickness: 4 + // 8. UI INFO + this.infoText = this.add.text(20, 20, '', { + fontFamily: 'monospace', fontSize: '20px', fill: '#ffffff', stroke: '#000000', strokeThickness: 4 + }).setDepth(10000); + + this.inventoryText = this.add.text(width - 150, 20, 'Seno: 0', { + fontFamily: 'monospace', fontSize: '20px', fill: '#ffff00', stroke: '#000000', strokeThickness: 4 }).setDepth(10000); } - generateGrass(w, h) { - // A) Small Tufts (Decoration) - High Density - // "Naključno razporedi trava_sop.png za vizualno gostoto" - for (let i = 0; i < 150; i++) { - let x = Phaser.Math.Between(0, w); - let y = Phaser.Math.Between(0, h); + generateInfiniteGrass(w, h) { + // "Density = 1.0f" -> High density, cover everything. + // We'll use a grid loop with some randomization offset + // Grass scale is 0.2, original image is ~512? + // 0.2 * 512 = ~100px width. + // Step size ~50px for overlap. - let tuft = this.add.image(x, y, 'grass_tuft'); - tuft.setScale(0.3 + Math.random() * 0.2); // Random size - tuft.setAlpha(0.9); - tuft.setDepth(y); // Simple Y-sort - } + const stepX = 40; + const stepY = 30; - // B) Tall Grass (Interactive) - Medium Density - // "Postavi visoka_trava.png ... dodaj nežen sinusni efekt" - for (let i = 0; i < 40; i++) { - let x = Phaser.Math.Between(50, w - 50); - let y = Phaser.Math.Between(50, h - 50); + for (let y = 0; y < h; y += stepY) { + for (let x = 0; x < w; x += stepX) { + // Add some randomness + let gx = x + Phaser.Math.Between(-10, 10); + let gy = y + Phaser.Math.Between(-10, 10); - let grass = this.add.image(x, y, 'grass_tall'); - grass.setScale(0.4); - grass.setOrigin(0.5, 1); // Pivot at bottom - grass.setDepth(y); + // create(x, y, key) + let grass = this.tallGrassGroup.create(gx, gy, 'grass_tall'); + grass.setScale(0.2); + grass.setOrigin(0.5, 0.95); + grass.setDepth(gy + 1); - // Custom properties for wind animation - grass.swaySpeed = 0.002 + Math.random() * 0.001; - grass.swayOffset = Math.random() * 100; + grass.swaySpeed = 0.002 + Math.random() * 0.001; + grass.swayOffset = Math.random() * 100; - this.grasses.push(grass); + this.allGrass.push(grass); + } } } - generateTrees(w, h) { - const treeTypes = ['tree_f1', 'tree_f2', 'tree_small', 'tree_medium', 'tree_large']; - - for (let i = 0; i < 12; i++) { - let x = Phaser.Math.Between(50, w - 50); - let y = Phaser.Math.Between(50, h - 50); - - // Randomly pick a growth stage - let type = Phaser.Math.RND.pick(treeTypes); - - let tree = this.add.image(x, y, type); - tree.setOrigin(0.5, 0.9); // Pivot near bottom - tree.setDepth(y); // Sort by Y - - // Scale adjustments to look good - if (type === 'tree_large') tree.setScale(0.8); - else if (type === 'tree_medium') tree.setScale(0.7); - else tree.setScale(0.6); - - // Subtle sway for trees too - tree.swaySpeed = 0.0005 + Math.random() * 0.0005; - tree.swayOffset = Math.random() * 100; - - this.trees.push(tree); + handleMow(player, grass) { + if (this.spaceKey.isDown) { + let hay = this.hayGroup.create(grass.x, grass.y + 10, 'hay_drop'); + hay.setScale(0.6); + hay.setDepth(1); + hay.setAngle(Phaser.Math.Between(-20, 20)); + grass.destroy(); + const index = this.allGrass.indexOf(grass); + if (index > -1) this.allGrass.splice(index, 1); } } + collectHay(player, hay) { + hay.destroy(); + this.hayCount++; + this.inventoryText.setText(`Seno: ${this.hayCount}`); + } + update(time, delta) { - // 1. WIND ANIMATION - // "Dodaj nežen sinusni efekt za plapolanje" - this.grasses.forEach(g => { - g.rotation = Math.sin((time * g.swaySpeed) + g.swayOffset) * 0.15; // Stronger sway for grass - }); + // MOVEMENT LOGIC + let currentSpeed = this.playerSpeed; + let isMoving = false; - this.trees.forEach(t => { - t.rotation = Math.sin((time * t.swaySpeed) + t.swayOffset) * 0.02; // Very subtle for trees - }); + // HIDING CHECK (Always effectively in grass in this mode, but let's check overlap technically) + let inTallGrass = this.physics.overlap(this.player, this.tallGrassGroup); + + if (inTallGrass) { + currentSpeed *= 0.6; + // "Player.CharacterAlpha = 0.4f" + this.player.setAlpha(0.4); + if (this.vignette.alpha < 1) this.vignette.alpha += 0.05; + } else { + // If he mows a clearing, he becomes visible + this.player.setAlpha(1.0); + if (this.vignette.alpha > 0) this.vignette.alpha -= 0.05; + } + + this.player.setVelocity(0); + if (this.cursors.left.isDown) { + this.player.setVelocityX(-currentSpeed); + this.player.setFlipX(true); + isMoving = true; + } else if (this.cursors.right.isDown) { + this.player.setVelocityX(currentSpeed); + this.player.setFlipX(false); + isMoving = true; + } + if (this.cursors.up.isDown) { + this.player.setVelocityY(-currentSpeed); + isMoving = true; + } else if (this.cursors.down.isDown) { + this.player.setVelocityY(currentSpeed); + isMoving = true; + } + + // AUDIO + if (isMoving && inTallGrass) { + if (time > this.lastStepTime + 350) { + this.sound.play('step_grass', { volume: 0.4 }); + this.lastStepTime = time; + } + } + + // VISUALS + this.player.setDepth(this.player.y); + this.allGrass.forEach(g => { if (g.active) g.rotation = Math.sin((time * g.swaySpeed) + g.swayOffset) * 0.15; }); - // 2. DAY/NIGHT CYCLE this.baseTime += (delta * 0.001 * this.timeSpeed); if (this.baseTime >= 24) this.baseTime = 0; this.updateLighting(this.baseTime); - // Update UI let hour = Math.floor(this.baseTime); let minute = Math.floor((this.baseTime % 1) * 60).toString().padStart(2, '0'); - this.infoText.setText(`Time: ${hour}:${minute}`); + this.infoText.setText(`Time: ${hour}:${minute}\n[Arrows] Premik\n[Space] Košnja`); } updateLighting(hour) { let alpha = 0; let color = 0x000000; - - // Simple Day/Night Logic - if (hour >= 20 || hour < 5) { - alpha = 0.7; // Night - color = 0x000022; - } else if (hour >= 5 && hour < 8) { - alpha = 0.3; // Dawn - color = 0xFF4500; - } else if (hour >= 18 && hour < 20) { - alpha = 0.4; // Dusk - color = 0xFF4500; - } else { - alpha = 0; // Day - } + if (hour >= 20 || hour < 5) { alpha = 0.7; color = 0x000022; } + else if (hour >= 5 && hour < 8) { alpha = 0.3; color = 0xFF4500; } + else if (hour >= 18 && hour < 20) { alpha = 0.4; color = 0xFF4500; } + else { alpha = 0; } this.dayNightOverlay.setFillStyle(color, alpha); } }