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