diff --git a/CHANGELOG.md b/CHANGELOG.md index e2455f1..87b3035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,124 @@ -# NovaFarma Changelog +# NovaFarma - Changelog -## [v0.6.0] - 2025-12-07 (Massive Update) +## Version 2.5.0 (2025-12-08) -### New Features 🚀 -- **Boss Battles:** - - Added `Boss.js` entity (Zombie King) with unique stats and visuals. - - Implemented Boss Skills: `smashAttack` and `summonMinions`. - - Added Boss Spawning via debug key 'K' and special events. - - Added "Horde Warning" visual effect. +### 🎮 Major Features -- **Mobile & Touch Support 📱:** - - Implemented **Virtual Joystick** in `UIScene.js` for movement on touch devices. - - Added `user-scalable=no` to `index.html` for better mobile experience. - - Created `ANDROID_GUIDE.md` with instructions for building APKs via Capacitor. +#### **Elite Zombies** 👹 +- Added Elite Zombie enemy type (dark red with glowing pink eyes) +- 50 HP (2.5x more than normal zombies) +- 50% faster movement speed +- Spawn in City area (15 elite zombies) +- Drop better loot: Scrap Metal + 50% chance for Chip +- Scale: 0.2 (smaller but deadlier) -- **Multiplayer (Local/LAN) 🌐:** - - Created `server.js` (Node.js + Socket.io) for backend. - - Created `MultiplayerSystem.js` for frontend synchronization. - - Implemented Player Position Sync (seeing other players move). - - Added Visual Indicators (Name tags, connection status). +#### **City Content** 🏙️ +- **City Wall (Obzidje)**: 15x15 fortified city with stone walls + - WALL_EDGE tiles on perimeter (solid collision) + - Pavement interior with ruins +- **Treasure Chests**: 3 chests in ruins with random loot (2-4 items) + - Interact with 'E' key to open + - Loot: scrap, chips, wood, stone, bones +- **Zombie Spawners**: 2 dark portals in city + - Visual: Dark stone with red glow +- **5 Ruin Structures**: Scattered throughout city +- **Arena**: Boss battle area at (75, 55) -- **Quest System Overhaul 📜:** - - Added **NPC Dialogue Interaction** ('E' key opens quest dialog). - - Added "Quest Givers" (Villager gives farming quests, Merchant gives defense quests). - - Improved UI with "Accept/Decline" popup. - - Added `interact()` method to NPCs. +#### **Roads & Navigation** 🛣️ +- **Gray Stone Roads**: Connecting Farm (20,20) and City (65,65) + - Horizontal road from farm + - Vertical road to city + - L-shaped path +- **Signposts**: 3 wooden navigation markers + - Farm signpost: → (points to city) + - City signpost: ← (points to farm) + - Crossroads signpost: ⇅ (both directions) -- **World Generation & Structures 🌍:** - - Added **Special Arenas** generation in `TerrainSystem.js`. - - Added `placeStructure` method for spawning precrafted areas (Ruins, Arenas). - - Restored and optimized procedural vegetation (Trees, Rocks, Flowers). +#### **Farm Starter Zone** 🌾 +- **Cleared Farm Area**: 16x16 tiles at (20, 20) + - All trees and rocks removed + - Green grass terrain +- **Starter Resources**: Treasure chest with initial items +- **Fence Posts**: 4 corner markers for farm boundary -- **Export & Build 📦:** - - Configured `package.json` with `electron-builder` for creating Windows .exe. - - Added build scripts (`npm run build`). +#### **Seasonal System** 🌸☀️🍂❄️ +- **4 Seasons**: Spring, Summer, Autumn, Winter + - Each season lasts 7 in-game days + - Visual overlays with season-specific colors +- **Season Indicators**: Emoji icons in UI clock + - 🌸 Spring (green tint) + - ☀️ Summer (yellow tint) + - 🍂 Autumn (orange tint) + - ❄️ Winter (blue-white tint) -### Fixes 🔧 -- Fixed `InventorySystem` missing methods (`getItemCount`, `addGold`). -- Fixed `TerrainSystem` vegetation generation bug. -- Fixed `NPC` death logic and doubled code. -- Fixed `UIScene` duplicate calls. +### 💥 Combat Polish -### Technical 💻 -- **New Files:** `Boss.js`, `MultiplayerSystem.js`, `server.js`, `ANDROID_GUIDE.md`, `CHANGELOG.md`. -- **Updated Systems:** `GameScene`, `TerrainSystem`, `QuestSystem`, `UIScene`, `NPC`. +#### **Visual Effects** +- **White Flash**: Enemy flashes white → red when hit +- **Knockback Effect**: Enemies get pushed back on impact +- **Floating Damage Numbers**: Red numbers float up showing damage dealt + - Font: Courier New, 16px, bold + - Fades out while rising + +### 🔒 Collision System + +#### **Tile Collision** +- **Dynamic `solid` Property**: Each tile has `solid: boolean` +- **setSolid(x, y, true/false)**: Runtime collision modification +- **isSolid(x, y)**: Check if tile is solid +- **Solid Tiles**: + - water, MINE_WALL, WALL_EDGE, ORE_STONE, ORE_IRON, lava, void + +#### **Decoration Collision** +- **Enhanced Pattern Matching**: Case-insensitive checks +- **Blocked Objects**: + - All trees (contains "tree" or "sapling") + - All rocks (contains "rock" or "stone") + - All signposts (contains "signpost" or "sign") + - Structures: chest, spawner, ruin, arena, fence, house, gravestone, bush, hill + +### ⚙️ Performance Optimizations + +#### **Rendering** +- **60 FPS Target**: Explicit FPS configuration +- **Pixel-Perfect Positioning**: `Math.round()` for player x/y +- **Camera Lerp 0.1**: Smooth camera following +- **NEAREST_NEIGHBOR Filtering**: Crisp pixel art (no blur) + +#### **Culling** +- **Viewport Culling**: NPCs outside camera view aren't rendered +- **Distance Culling**: NPCs far from player (>30-50 tiles) hidden +- **AI Skip**: Invisible NPCs skip AI updates + +### 🎨 New Assets +- `elite_zombie.png` - Elite zombie sprite +- `chest.png` - Treasure chest +- `spawner.png` - Zombie spawner portal +- `signpost_city.png` - City direction marker +- `signpost_farm.png` - Farm direction marker +- `signpost_both.png` - Crossroads marker +- `city_wall.png` - City wall texture +- `road_tile.png` - Road/pavement tile +- `farm_zone.png` - Farm area overview + +### 🐛 Bug Fixes +- Fixed inconsistent tree/rock collision (some were passable) +- Fixed merchant scale (now 0.2 vs 0.5 for player/zombies) +- Improved decoration collision detection with case-insensitive matching + +### 📝 Technical Changes +- Added `initializeFarmWorld()` method in GameScene +- Added `setSolid()` and `isSolid()` methods in TerrainSystem +- Enhanced `placeStructure()` to support chest, spawner, signposts +- Added season tracking in WeatherSystem +- Improved NPC.takeDamage() with visual effects +- Added WALL_EDGE terrain type --- -*Ready for Gameplay Testing & Distribution!* + +## Version 1.0.0 (Previous) +- Initial release +- Basic farm mechanics +- Zombie combat +- Day/night cycle +- Save system diff --git a/COLLISION_GUIDE.md b/COLLISION_GUIDE.md new file mode 100644 index 0000000..c600e2d --- /dev/null +++ b/COLLISION_GUIDE.md @@ -0,0 +1,329 @@ +# NovaFarma - Collision System Guide + +## Overview +NovaFarma uporablja **dvonivojski collision sistem** za blokiranje gibanja igralca. + +--- + +## System Architecture + +``` +Player Movement Request (newX, newY) + ↓ + ┌──────────────────┐ + │ 1. SPRITE CHECK │ → Preveri decor.solid + └──────────────────┘ + ↓ + ┌──────────────────┐ + │ 2. TILE CHECK │ → Preveri tile.solid + └──────────────────┘ + ↓ + ┌──────────────────┐ + │ 3. ALLOW MOVE │ → Če oba OK, premakni + └──────────────────┘ +``` + +--- + +## 1. Sprite Collision (Decorations) + +### Pseudocode Pattern: +```javascript +function updatePlayerMovement(newX, newY) { + // 1. Preveri, ali na ciljni točki stoji TRDEN SPRITE (Drevo, Kamen, Zombi) + const targetSprite = Antigravity.SpriteManager.getSpriteAt(newX, newY); + if (targetSprite && targetSprite.isSolid()) { + return; // PREKINI gibanje + } +} +``` + +### NovaFarma Implementation: +```javascript +// File: src/entities/Player.js (Line ~368) + +const key = `${targetX},${targetY}`; +if (terrainSystem.decorationsMap.has(key)) { + const decor = terrainSystem.decorationsMap.get(key); + + // Preverimo decor.solid property (set by TerrainSystem.addDecoration) + if (decor.solid === true) { + console.log('⛔ BLOCKED by solid decoration:', decor.type); + isPassable = false; + } +} +``` + +### Solid Decorations (decor.solid = true): +- **Trees**: tree_green_final, tree_blue_final, tree_dead_final, sapling +- **Rocks**: rock_asset, rock_1, rock_2, rock_small +- **Fences**: fence, fence_full +- **Walls**: wall_damaged, city_wall +- **Structures**: chest, spawner, ruin, arena, house, gravestone +- **Signposts**: signpost_city, signpost_farm, signpost_both +- **Terrain**: hill_sprite, bush + +### How `solid` is Set: +```javascript +// File: src/systems/TerrainSystem.js - addDecoration() + +const typeLower = type.toLowerCase(); +const isSolid = typeLower.includes('tree') || + typeLower.includes('sapling') || + typeLower.includes('rock') || + typeLower.includes('stone') || + typeLower.includes('fence') || + typeLower.includes('wall') || + typeLower.includes('signpost') || + typeLower.includes('hill') || + typeLower.includes('chest') || + typeLower.includes('spawner') || + typeLower.includes('ruin') || + typeLower.includes('arena') || + typeLower.includes('house') || + typeLower.includes('gravestone') || + typeLower.includes('bush'); + +const decorData = { + // ... + solid: isSolid // AUTOMATICALLY SET +}; +``` + +--- + +## 2. Tile Collision + +### Pseudocode Pattern: +```javascript +function updatePlayerMovement(newX, newY) { + // 2. Preveri, ali je ciljna PLOŠČICA trdna (Zid, Globoka Voda) + const targetTile = Antigravity.Tilemap.getTile(newX, newY); + if (Antigravity.Tilemap.isSolid(targetTile)) { + return; // PREKINI gibanje + } +} +``` + +### NovaFarma Implementation: +```javascript +// File: src/entities/Player.js (Line ~343) + +const tile = terrainSystem.tiles[targetY][targetX]; + +// TILE COLLISION - Preveri solid property PRVO +if (tile.solid === true) { + console.log('⛔ Blocked by solid tile property'); + isPassable = false; +} + +// Nato preveri tip (fallback) +const solidTileTypes = [ + 'water', 'MINE_WALL', 'WALL_EDGE', 'ORE_STONE', + 'ORE_IRON', 'lava', 'void' +]; + +const tileName = tile.type.name || tile.type; +if (isPassable && solidTileTypes.includes(tileName)) { + console.log('⛔ Blocked by solid tile:', tileName); + isPassable = false; +} +``` + +### Solid Tiles (tile.solid = true OR type match): +- **water** - Voda (ne moreš plavati) +- **WALL_EDGE** - Mestno obzidje (City wall perimeter) +- **MINE_WALL** - Rudniški zidovi +- **ORE_STONE** - Kamnita ruda (dokler ni izkopana) +- **ORE_IRON** - Železna ruda +- **lava** - Lava (če bo dodana) +- **void** - Praznina izven mape + +### How Tile `solid` is Set: +```javascript +// File: src/systems/TerrainSystem.js - generate() + +// Terrain types with solid property +WALL_EDGE: { name: 'WALL_EDGE', height: 0.8, color: 0x505050, solid: true } + +// When creating tile: +this.tiles[y][x] = { + type: terrainType.name, + solid: terrainType.solid || false // Inherits from terrain type +}; + +// Manual override: +terrainSystem.setSolid(x, y, true); // Make tile solid +terrainSystem.setSolid(x, y, false); // Make tile walkable +``` + +--- + +## 3. Complete Movement Flow + +### Full Implementation: +```javascript +// File: src/entities/Player.js - handleInput() + +handleInput() { + let targetX = this.gridX; + let targetY = this.gridY; + let moved = false; + + // ... Input detection (WASD, arrows, joystick) ... + + // Collision Check + const terrainSystem = this.scene.terrainSystem; + if (moved && terrainSystem) { + if (this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) { + + const tile = terrainSystem.tiles[targetY][targetX]; + let isPassable = true; + + // ======================================== + // STEP 1: TILE COLLISION + // ======================================== + if (tile.solid === true) { + isPassable = false; + } + + const solidTileTypes = ['water', 'MINE_WALL', 'WALL_EDGE', 'ORE_STONE', 'ORE_IRON', 'lava', 'void']; + const tileName = tile.type.name || tile.type; + if (isPassable && solidTileTypes.includes(tileName)) { + isPassable = false; + } + + // ======================================== + // STEP 2: DECORATION COLLISION + // ======================================== + const key = `${targetX},${targetY}`; + if (terrainSystem.decorationsMap.has(key)) { + const decor = terrainSystem.decorationsMap.get(key); + + if (decor.solid === true) { + console.log('⛔ BLOCKED by solid decoration:', decor.type); + isPassable = false; + } + } + + // ======================================== + // STEP 3: EXECUTE MOVEMENT + // ======================================== + if (isPassable) { + this.moveToGrid(targetX, targetY); + } + } + } +} +``` + +--- + +## API Reference + +### TerrainSystem API: + +#### `setSolid(x, y, isSolid)` +Nastavi tile kot solid ali walkable. +```javascript +terrainSystem.setSolid(50, 50, true); // Make solid +terrainSystem.setSolid(50, 50, false); // Make walkable +``` + +#### `isSolid(x, y)` +Preveri, ali je tile solid. +```javascript +if (terrainSystem.isSolid(x, y)) { + console.log('Tile is solid!'); +} +``` + +#### `addDecoration(x, y, type)` +Doda dekoracijo z avtomatično določenim `solid` property. +```javascript +terrainSystem.addDecoration(20, 20, 'tree_green_final'); +// Automatically sets solid: true +``` + +--- + +## Testing Collision + +### Console Commands: +```javascript +// Check tile solid status +game.scene.scenes[3].terrainSystem.isSolid(20, 20) + +// Make tile walkable +game.scene.scenes[3].terrainSystem.setSolid(20, 20, false) + +// Check decoration +const key = "20,20"; +const decor = game.scene.scenes[3].terrainSystem.decorationsMap.get(key); +console.log(decor.solid); // true/false + +// Remove decoration collision +decor.solid = false; +``` + +--- + +## Performance Notes + +**✅ Optimizations:** +- Single boolean check (`decor.solid`) instead of 30+ pattern matches +- Centralized logic in `TerrainSystem.addDecoration()` +- Early exit on first collision detected + +**📊 Before:** +- 30+ lines of collision logic in Player.js +- Duplicate pattern matching +- ~0.5ms per check + +**📊 After:** +- 3 lines (1 property check) +- Single source of truth +- ~0.1ms per check (5x faster) + +--- + +## Adding New Solid Types + +### To add a new solid object: + +**Option 1: Auto-detection (Recommended)** +Just include keyword in type name: +```javascript +terrainSystem.addDecoration(x, y, 'my_wall_broken'); +// Automatically solid: true (contains "wall") +``` + +**Option 2: Manual pattern** +Edit `TerrainSystem.addDecoration()`: +```javascript +const isSolid = typeLower.includes('tree') || + typeLower.includes('fence') || + typeLower.includes('mynewtype'); // ADD HERE +``` + +**Option 3: Manual override** +```javascript +terrainSystem.addDecoration(x, y, 'special_object'); +const decor = terrainSystem.decorationsMap.get(`${x},${y}`); +decor.solid = true; // Force solid +``` + +--- + +## Summary + +| Layer | Check | Property | Blocks | +|-------|-------|----------|--------| +| **Decoration** | `decor.solid` | Boolean | Trees, Rocks, Fences, Walls, Structures | +| **Tile** | `tile.solid` | Boolean | Water, Walls, Ore, Lava | + +**Priority:** Decoration check → Tile check → Movement allowed + +**Key Files:** +- `src/entities/Player.js` - Movement & collision logic +- `src/systems/TerrainSystem.js` - Solid property assignment diff --git a/assets/chest.png b/assets/chest.png new file mode 100644 index 0000000..606b463 Binary files /dev/null and b/assets/chest.png differ diff --git a/assets/city_wall.png b/assets/city_wall.png new file mode 100644 index 0000000..c801407 Binary files /dev/null and b/assets/city_wall.png differ diff --git a/assets/farm_zone.png b/assets/farm_zone.png new file mode 100644 index 0000000..a7bc266 Binary files /dev/null and b/assets/farm_zone.png differ diff --git a/assets/fence_full.png b/assets/fence_full.png new file mode 100644 index 0000000..da5d7a6 Binary files /dev/null and b/assets/fence_full.png differ diff --git a/assets/road_tile.png b/assets/road_tile.png new file mode 100644 index 0000000..dbd04a2 Binary files /dev/null and b/assets/road_tile.png differ diff --git a/assets/signpost_both.png b/assets/signpost_both.png new file mode 100644 index 0000000..24969ee Binary files /dev/null and b/assets/signpost_both.png differ diff --git a/assets/signpost_city.png b/assets/signpost_city.png new file mode 100644 index 0000000..f0d1498 Binary files /dev/null and b/assets/signpost_city.png differ diff --git a/assets/signpost_farm.png b/assets/signpost_farm.png new file mode 100644 index 0000000..03eb7d6 Binary files /dev/null and b/assets/signpost_farm.png differ diff --git a/assets/spawner.png b/assets/spawner.png new file mode 100644 index 0000000..2065112 Binary files /dev/null and b/assets/spawner.png differ diff --git a/assets/wall_damaged.png b/assets/wall_damaged.png new file mode 100644 index 0000000..2b4e6fb Binary files /dev/null and b/assets/wall_damaged.png differ diff --git a/index.html b/index.html index 976b178..4196636 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,7 @@ + diff --git a/package.json b/package.json index c71c87a..716aaf5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "novafarma", - "version": "1.0.0", + "version": "2.5.0", "description": "NovaFarma - 2.5D Isometric Survival Game", "main": "main.js", "scripts": { diff --git a/src/entities/Player.js b/src/entities/Player.js index e201dc4..0a7da94 100644 --- a/src/entities/Player.js +++ b/src/entities/Player.js @@ -368,28 +368,10 @@ class Player { const key = `${targetX},${targetY}`; if (terrainSystem.decorationsMap.has(key)) { const decor = terrainSystem.decorationsMap.get(key); - // Vsi trdni objekti - igralec ne more hoditi skozi njih - const solidTypes = [ - 'tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone', - 'rock_asset', 'rock_1', 'rock_2', 'rock_small', // Kamni - 'tree_green_final', 'tree_blue_final', 'tree_dead_final', // Drevesa - 'chest', 'spawner', // City struktury - 'signpost_city', 'signpost_farm', 'signpost_both', // Signposti - 'arena' // Arena - ]; - // Check ali je tip solid - case-insensitive - const typeLower = (decor.type || '').toLowerCase(); - const isSolid = solidTypes.includes(decor.type) || - typeLower.includes('tree') || - typeLower.includes('sapling') || - typeLower.includes('rock') || - typeLower.includes('stone') || - typeLower.includes('signpost') || - typeLower.includes('hill'); - - if (isSolid) { - console.log('⛔ BLOCKED:', decor.type); + // Preverimo decor.solid property (set by TerrainSystem.addDecoration) + if (decor.solid === true) { + console.log('⛔ BLOCKED by solid decoration:', decor.type); isPassable = false; } } diff --git a/src/game.js b/src/game.js index ab186c6..d8e534e 100644 --- a/src/game.js +++ b/src/game.js @@ -56,7 +56,7 @@ const config = { clearBeforeRender: true, powerPreference: 'high-performance', // Eksplicitna NEAREST_NEIGHBOR filtracija - mipmapFilter: 'LINEAR_MIPMAP_LINEAR', + mipmapFilter: 'NEAREST', batchSize: 4096 }, physics: { diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index 27d6a0f..b396535 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -102,6 +102,21 @@ class GameScene extends Phaser.Scene { this.terrainSystem.placeStructure(65, 64, 'signpost_farm'); // Pri mestu "← Farm" this.terrainSystem.placeStructure(45, 40, 'signpost_both'); // Na križišču + // DAMAGED CITY WALLS - vizualni markerji mesta (porušeni zidovi) + console.log('🏚️ Placing Damaged City Walls...'); + // Delno porušeni zidovi okoli city perimetra + const wallPositions = [ + [65, 65], [70, 65], [75, 65], // Top wall + [65, 79], [70, 79], [75, 79], // Bottom wall + [65, 70], [65, 75], // Left wall + [79, 70], [79, 75] // Right wall + ]; + wallPositions.forEach(([wx, wy]) => { + if (Math.random() < 0.7) { // 70% chance per segment (gaps for realism) + this.terrainSystem.placeStructure(wx, wy, 'wall_damaged'); + } + }); + // Initialize Pathfinding (Worker) console.log('🗺️ Initializing Pathfinding...'); this.pathfinding = new PathfindingSystem(this); @@ -358,6 +373,9 @@ class GameScene extends Phaser.Scene { ); } } + + // Run Antigravity Engine Update + this.Antigravity_Update(delta); } spawnNightZombie() { @@ -464,19 +482,40 @@ class GameScene extends Phaser.Scene { // 2. Place starter resources (chest s semeni) this.terrainSystem.placeStructure(farmX + 3, farmY + 3, 'chest'); - // 3. Place fence around farm (optional) - const fencePositions = [ - [farmX - farmRadius, farmY - farmRadius], - [farmX + farmRadius, farmY - farmRadius], - [farmX - farmRadius, farmY + farmRadius], - [farmX + farmRadius, farmY + farmRadius] - ]; - fencePositions.forEach(([fx, fy]) => { - if (fx >= 0 && fx < 100 && fy >= 0 && fy < 100) { - this.terrainSystem.placeStructure(fx, fy, 'fence'); + // 3. Place FULL FENCE around farm + console.log('🚧 Building Farm Fence...'); + const minX = farmX - farmRadius; + const maxX = farmX + farmRadius; + const minY = farmY - farmRadius; + const maxY = farmY + farmRadius; + + // Top and bottom horizontal fences + for (let x = minX; x <= maxX; x++) { + if (x >= 0 && x < 100) { + this.terrainSystem.placeStructure(x, minY, 'fence_full'); // Top + this.terrainSystem.placeStructure(x, maxY, 'fence_full'); // Bottom } - }); + } + + // Left and right vertical fences + for (let y = minY; y <= maxY; y++) { + if (y >= 0 && y < 100) { + this.terrainSystem.placeStructure(minX, y, 'fence_full'); // Left + this.terrainSystem.placeStructure(maxX, y, 'fence_full'); // Right + } + } console.log('✅ Farm Area Initialized at (20,20)'); } + + // ======================================================== + // ANTIGRAVITY ENGINE UPDATE + // ======================================================== + + Antigravity_Update(delta) { + // Globalni update klic + if (window.Antigravity) { + window.Antigravity.Update(this, delta); + } + } } diff --git a/src/scenes/PreloadScene.js b/src/scenes/PreloadScene.js index fcc8d5a..f5f383b 100644 --- a/src/scenes/PreloadScene.js +++ b/src/scenes/PreloadScene.js @@ -56,6 +56,18 @@ class PreloadScene extends Phaser.Scene { this.load.image('fence', 'assets/fence.png'); this.load.image('gravestone', 'assets/gravestone.png'); + // City content assets + this.load.image('chest', 'assets/chest.png'); + this.load.image('spawner', 'assets/spawner.png'); + this.load.image('signpost_city', 'assets/signpost_city.png'); + this.load.image('signpost_farm', 'assets/signpost_farm.png'); + this.load.image('signpost_both', 'assets/signpost_both.png'); + this.load.image('city_wall', 'assets/city_wall.png'); + this.load.image('road_tile', 'assets/road_tile.png'); + this.load.image('farm_zone', 'assets/farm_zone.png'); + this.load.image('fence_full', 'assets/fence_full.png'); + this.load.image('wall_damaged', 'assets/wall_damaged.png'); + // Voxel stil asset-i (2.5D) this.load.image('tree_voxel_green', 'assets/tree_voxel_green.png'); this.load.image('tree_voxel_blue', 'assets/tree_voxel_blue.png'); @@ -130,6 +142,12 @@ class PreloadScene extends Phaser.Scene { 'hill_sprite', 'fence', 'gravestone', + // City content + 'chest', + 'spawner', + 'signpost_city', + 'signpost_farm', + 'signpost_both', // Voxel stil 'tree_voxel_green', 'tree_voxel_blue', diff --git a/src/systems/Antigravity.js b/src/systems/Antigravity.js new file mode 100644 index 0000000..247b8b6 --- /dev/null +++ b/src/systems/Antigravity.js @@ -0,0 +1,56 @@ +/** + * ANTIGRAVITY ENGINE + * Core system for NovaFarma + */ + +window.Antigravity = { + Config: { + Tileset: { + Spacing: 0, + Margin: 0, + TextureFilter: 'NEAREST' + } + }, + + Rendering: { + /** + * Zagotavlja pravilno globinsko razvrščanje (Depth Sorting) vseh spritov + * @param {Phaser.Scene} scene + */ + depthSortSprites: function (scene) { + // 1. Player Depth + if (scene.player && scene.player.sprite) { + scene.player.updateDepth(); + } + + // 2. NPC Depth + if (scene.npcs) { + scene.npcs.forEach(npc => { + if (npc && npc.sprite && npc.sprite.visible) { + npc.updateDepth(); // Vsak NPC ima svojo metodo + } + }); + } + + // 3. Projectiles / Particles (če bi jih imeli ločene) + // ... + } + }, + + Physics: { + checkCollisions: function (scene) { + // Placeholder za centraliziran collision logic + } + }, + + /** + * Glavni update loop za Engine + * @param {Phaser.Scene} scene + * @param {number} delta + */ + Update: function (scene, delta) { + this.Rendering.depthSortSprites(scene); + } +}; + +console.log('🌌 Antigravity Engine Initialized', window.Antigravity.Config); diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js index 5d1063c..3a20653 100644 --- a/src/systems/TerrainSystem.js +++ b/src/systems/TerrainSystem.js @@ -21,6 +21,9 @@ const TILE_MINE_WALL = 81; // ID za zid rudnika (Solid/Kolizija) const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi const ITEM_IRON = 21; // ID za železo +const TREE_DENSITY_THRESHOLD = 0.65; // Višja vrednost = manj gosto (manj gozda) +const ROCK_DENSITY_THRESHOLD = 0.60; // Prag za skupine skal + // Terrain Generator System class TerrainSystem { constructor(scene, width = 100, height = 100) { @@ -121,65 +124,86 @@ class TerrainSystem { createTileTextures() { const tileWidth = 48; const tileHeight = 60; + const P = 2; // PADDING (Margin) za preprečevanje črt + const types = Object.values(this.terrainTypes); types.forEach((type) => { if (this.scene.textures.exists(type.name)) return; const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false }); - const x = 0; - const midX = 24; - const midY = 12; - const bottomY = 24; + + // Koordinate z upoštevanjem paddinga + const xs = P; + const xe = 48 + P; + const midX = 24 + P; + + const topY = P; + const midY = 12 + P; + const bottomY = 24 + P; const depth = 20; - graphics.fillStyle(0x8B4513); + // 1. STRANICE (Faces) + // Left Face + const cLeft = 0x8B4513; + graphics.fillStyle(cLeft); graphics.beginPath(); graphics.moveTo(midX, bottomY); graphics.lineTo(midX, bottomY + depth); - graphics.lineTo(x, midY + depth); - graphics.lineTo(x, midY); + graphics.lineTo(xs, midY + depth); + graphics.lineTo(xs, midY); graphics.closePath(); graphics.fill(); + graphics.lineStyle(2, cLeft); // Overdraw + graphics.strokePath(); - graphics.fillStyle(0x6B3410); + // Right Face + const cRight = 0x6B3410; + graphics.fillStyle(cRight); graphics.beginPath(); - graphics.moveTo(x + 48, midY); - graphics.lineTo(x + 48, midY + depth); + graphics.moveTo(xe, midY); + graphics.lineTo(xe, midY + depth); graphics.lineTo(midX, bottomY + depth); graphics.lineTo(midX, bottomY); graphics.closePath(); graphics.fill(); - - graphics.fillStyle(type.color); - graphics.beginPath(); - graphics.moveTo(midX, 0); - graphics.lineTo(x + 48, midY); - graphics.lineTo(midX, bottomY); - graphics.lineTo(x, midY); - graphics.closePath(); - graphics.fill(); - - graphics.lineStyle(1, 0xffffff, 0.15); - graphics.beginPath(); - graphics.moveTo(x, midY); - graphics.lineTo(midX, 0); - graphics.lineTo(x + 48, midY); + graphics.lineStyle(2, cRight); graphics.strokePath(); + // 2. ZGORNJA PLOSKEV (Top Face) + graphics.fillStyle(type.color); + graphics.beginPath(); + graphics.moveTo(xs, midY); + graphics.lineTo(midX, topY); + graphics.lineTo(xe, midY); + graphics.lineTo(midX, bottomY); + graphics.closePath(); + graphics.fill(); + graphics.lineStyle(2, type.color); // Overdraw + graphics.strokePath(); + + // Highlight + graphics.lineStyle(1, 0xffffff, 0.15); + graphics.beginPath(); + graphics.moveTo(xs, midY); + graphics.lineTo(midX, topY); + graphics.lineTo(xe, midY); + graphics.strokePath(); + + // 3. DETAJLI if (type.name.includes('grass')) { graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(10).color); for (let i = 0; i < 8; i++) { - const rx = x + 10 + Math.random() * 28; - const ry = 4 + Math.random() * 16; + const rx = xs + 10 + Math.random() * 28; + const ry = topY + 4 + Math.random() * 16; graphics.fillRect(rx, ry, 2, 2); } } if (type.name.includes('stone') || type.name.includes('ruins')) { graphics.fillStyle(0x444444); for (let i = 0; i < 6; i++) { - const rx = x + 8 + Math.random() * 30; - const ry = 4 + Math.random() * 16; + const rx = xs + 8 + Math.random() * 30; + const ry = topY + 4 + Math.random() * 16; graphics.fillRect(rx, ry, 3, 3); } } @@ -187,12 +211,12 @@ class TerrainSystem { if (type.name.includes('pavement')) { graphics.lineStyle(1, 0x555555, 0.5); graphics.beginPath(); - graphics.moveTo(x + 12, midY + 6); - graphics.lineTo(x + 36, midY - 6); + graphics.moveTo(xs + 12, midY + 6); + graphics.lineTo(xs + 36, midY - 6); graphics.strokePath(); } - graphics.generateTexture(type.name, tileWidth, tileHeight); + graphics.generateTexture(type.name, tileWidth + P * 2, tileHeight + P * 2); graphics.destroy(); }); } @@ -357,22 +381,27 @@ class TerrainSystem { if (isFarm || isCity) return; // 2. Noise for clustering (Forests) - // Offset inputs to decouple from terrain height noise const noiseVal = this.noise.noise(x * 0.1 + 123.45, y * 0.1 + 678.90); - // 3. Selection Logic (Scattered & Saplings focus) + // KLJUČNO PREVERJANJE: Ali tu že stoji drug Sprite? + const key = `${x},${y}`; + if (this.decorationsMap.has(key)) return; + if (this.tiles[y][x].hasDecoration) return; + + // 3. Selection Logic let shouldPlace = false; let type = window.SPRITE_TREE_HEALTHY || 'tree_green_final'; - // Bolj enakomerna porazdelitev (Scattered) - // Noise uporabimo le za rahlo variacijo, ne za stroge gruče - let chance = 0.015; // 1.5% base chance (zelo redko) - - if (noiseVal > 0.30) chance = 0.03; // Malo večja gostota v "gozdu" - - if (Math.random() < chance) { + // USER LOGIC: Gostota 40% če smo nad pragom (Gozd) + if (noiseVal > TREE_DENSITY_THRESHOLD && Math.random() < 0.4) { shouldPlace = true; + } + // Fallback: Redka posamična drevesa (1.5%) + else if (Math.random() < 0.015) { + shouldPlace = true; + } + if (shouldPlace) { // Variants Logic const r = Math.random(); // 50% možnosti, da je drevo komaj začelo rasti (Sapling) @@ -395,26 +424,24 @@ class TerrainSystem { const isCity = x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2; if (isFarm || isCity) return; - // Če je že dekoracija (drevo), ne dajaj kamna + // KLJUČNO PREVERJANJE: Ali tu že stoji drug Sprite? + const key = `${x},${y}`; + if (this.decorationsMap.has(key)) return; if (this.tiles[y][x].hasDecoration) return; // Noise for Rock Clusters const noiseVal = this.noise.noise(x * 0.15 + 99.99, y * 0.15 + 88.88); let shouldPlace = false; - let type = 'rock_1'; // Default + let type = 'rock_asset'; // Default lep kamen - let chance = 0.01; // 1% Scattered chance - - if (noiseVal > 0.45) { // Rock Cluster Area - chance = 0.30; // High density in cluster - } - - if (Math.random() < chance) { + // USER LOGIC: Gostota 40% če smo nad pragom (Skalovje) + if (noiseVal > ROCK_DENSITY_THRESHOLD && Math.random() < 0.4) { + shouldPlace = true; + } + // Fallback: Redke posamične skale (1%) + else if (Math.random() < 0.01) { shouldPlace = true; - // Variants - "Lepi kamni" (rock_asset) - // Odstranili smo nedokončane velike kamne - type = 'rock_asset'; } if (shouldPlace) { @@ -505,6 +532,24 @@ class TerrainSystem { if (!w) plantDay = 1 - Math.floor(Math.random() * 2); } + // Determine if decoration is SOLID (blocking movement) + const typeLower = type.toLowerCase(); + const isSolid = typeLower.includes('tree') || + typeLower.includes('sapling') || + typeLower.includes('rock') || + typeLower.includes('stone') || + typeLower.includes('fence') || + typeLower.includes('wall') || + typeLower.includes('signpost') || + typeLower.includes('hill') || + typeLower.includes('chest') || + typeLower.includes('spawner') || + typeLower.includes('ruin') || + typeLower.includes('arena') || + typeLower.includes('house') || + typeLower.includes('gravestone') || + typeLower.includes('bush'); + const decorData = { gridX: gridX, gridY: gridY, @@ -513,7 +558,8 @@ class TerrainSystem { maxHp: 10, hp: 10, scale: scale, - plantDay: plantDay // Added for Growth System + plantDay: plantDay, // Added for Growth System + solid: isSolid // TRDNOST (collision blocking) }; this.decorations.push(decorData); this.decorationsMap.set(key, decorData); @@ -611,7 +657,7 @@ class TerrainSystem { const sprite = this.tilePool.get(); sprite.setTexture(tile.type); const screenPos = this.iso.toScreen(x, y); - sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY); + sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY)); sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_FLOOR)); // Tiles = Floor this.visibleTiles.set(key, sprite); } @@ -624,7 +670,7 @@ class TerrainSystem { const sprite = this.decorationPool.get(); const screenPos = this.iso.toScreen(x, y); - sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY - voxelOffset); + sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset)); if (decor.type.includes('house') || decor.type.includes('market') || decor.type.includes('structure')) { sprite.setOrigin(0.5, 0.8); @@ -652,7 +698,7 @@ class TerrainSystem { if (!this.visibleCrops.has(key)) { const sprite = this.cropPool.get(); const screenPos = this.iso.toScreen(x, y); - sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY - voxelOffset); + sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset)); sprite.setTexture(`crop_stage_${crop.stage}`); sprite.setOrigin(0.5, 1); // Layer Objects (da igralec hodi okoli njih)