diff --git a/assets/elite_zombie.png b/assets/elite_zombie.png new file mode 100644 index 0000000..3e8dccc Binary files /dev/null and b/assets/elite_zombie.png differ diff --git a/src/entities/NPC.js b/src/entities/NPC.js index c77cf1a..712cf0d 100644 --- a/src/entities/NPC.js +++ b/src/entities/NPC.js @@ -24,8 +24,17 @@ class NPC { this.pauseTime = 0; this.maxPauseTime = 2000; this.attackCooldownTimer = 0; - this.hp = 20; - this.maxHp = 20; + + // Elite Zombie Stats + if (type === 'elite_zombie') { + this.hp = 50; // 2.5x več HP + this.maxHp = 50; + this.moveSpeed = 150; // 50% hitrejši + this.gridMoveTime = 200; // Hitrejši premiki + } else { + this.hp = 20; + this.maxHp = 20; + } // Kreira sprite this.createSprite(); @@ -128,6 +137,14 @@ class NPC { texKey = 'merchant_texture'; // Fallback na proceduralno } console.log('🏪 Creating MERCHANT NPC with texture:', texKey); + } else if (this.type === 'elite_zombie') { + // Elite Zombie sprite + if (this.scene.textures.exists('elite_zombie')) { + texKey = 'elite_zombie'; + } else { + texKey = 'npc_elite_zombie'; // Fallback na proceduralno + } + console.log('👹 Creating ELITE ZOMBIE with texture:', texKey); } else if (!this.scene.textures.exists(texKey)) { TextureGenerator.createNPCSprite(this.scene, texKey, this.type); } @@ -144,8 +161,10 @@ class NPC { if (isAnimated) { this.sprite.setScale(1.5); } else { - // Merchant manjši, ostali večji - const scale = (this.type === 'merchant') ? 0.2 : 0.5; + // Scale po tipu + let scale = 0.5; // Default + if (this.type === 'merchant') scale = 0.2; + else if (this.type === 'elite_zombie') scale = 0.2; // Elite manjši this.sprite.setScale(scale); } @@ -206,7 +225,27 @@ class NPC { } update(delta) { - // 1. Distance Culling + // 1. Viewport Culling - če sprite ni viden na kameri, preskoči risanje + const camera = this.scene.cameras.main; + if (camera && this.sprite) { + const worldView = camera.worldView; + const isVisible = Phaser.Geom.Rectangle.Overlaps(worldView, this.sprite.getBounds()); + + if (!isVisible && this.sprite.visible) { + this.sprite.setVisible(false); + if (this.healthBar) { + this.healthBar.setVisible(false); + this.healthBarBg.setVisible(false); + } + if (this.eyesGroup) this.eyesGroup.setVisible(false); + return; // Preskoči AI če ni viden + } else if (isVisible && !this.sprite.visible) { + this.sprite.setVisible(true); + if (this.eyesGroup) this.eyesGroup.setVisible(true); + } + } + + // 2. Distance Culling if (this.scene.player) { const playerPos = this.scene.player.getPosition(); const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y); @@ -231,7 +270,7 @@ class NPC { } } - // 2. Optimization: Update depth ONLY if moving + // 3. Optimization: Update depth ONLY if moving if (this.isMoving) { this.updateDepth(); this.updatePosition(); @@ -544,19 +583,73 @@ class NPC { this.healthBar.fillRect(-16, -70, 32 * percent, 4); } + // WHITE FLASH Effect if (this.sprite) { - this.sprite.setTint(0xff0000); - this.scene.time.delayedCall(100, () => { + this.sprite.setTint(0xFFFFFF); // White flash + this.scene.time.delayedCall(50, () => { if (this.sprite) { - this.sprite.clearTint(); - // Re-apply tamed tint if tamed - if (this.state === 'TAMED' || this.state === 'FOLLOW' || this.state === 'STAY') { - this.sprite.setTint(0xAAFFAA); - } + this.sprite.setTint(0xFF0000); // Red flash + this.scene.time.delayedCall(50, () => { + if (this.sprite) { + this.sprite.clearTint(); + if (this.state === 'TAMED' || this.state === 'FOLLOW' || this.state === 'STAY') { + this.sprite.setTint(0xAAFFAA); + } + } + }); } }); } + // KNOCKBACK Effect + if (this.sprite && this.scene.player) { + const knockbackDist = 10; + const angle = Phaser.Math.Angle.Between( + this.scene.player.sprite.x, + this.scene.player.sprite.y, + this.sprite.x, + this.sprite.y + ); + const knockX = Math.cos(angle) * knockbackDist; + const knockY = Math.sin(angle) * knockbackDist; + + this.scene.tweens.add({ + targets: this.sprite, + x: this.sprite.x + knockX, + y: this.sprite.y + knockY, + duration: 100, + yoyo: true, + ease: 'Quad.easeOut' + }); + } + + // FLOATING DAMAGE NUMBERS + if (this.sprite) { + const dmgTxt = this.scene.add.text( + this.sprite.x, + this.sprite.y - 40, + `-${amount}`, + { + fontSize: '16px', + fontFamily: 'Courier New', + color: '#FF0000', + stroke: '#000', + strokeThickness: 3, + fontStyle: 'bold' + } + ); + dmgTxt.setOrigin(0.5); + dmgTxt.setDepth(999999); + this.scene.tweens.add({ + targets: dmgTxt, + y: dmgTxt.y - 30, + alpha: 0, + duration: 800, + ease: 'Cubic.easeOut', + onComplete: () => dmgTxt.destroy() + }); + } + console.log(`Zombie HP: ${this.hp}`); if (this.hp <= 0) { @@ -572,12 +665,19 @@ class NPC { this.scene.soundManager.playDeath(); } - // Spawn loot - BONE + // Spawn loot - Elite zombies drop better items! if (this.scene.lootSystem) { - this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, 'item_bone'); + const lootItem = (this.type === 'elite_zombie') ? 'item_scrap' : 'item_bone'; + this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, lootItem); + + // Elite bonus: 50% chance for extra chip + if (this.type === 'elite_zombie' && Math.random() < 0.5) { + this.scene.lootSystem.spawnLoot(this.gridX, this.gridY, 'item_chip'); + } } else if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) { // Fallback - this.scene.interactionSystem.spawnLoot(this.gridX, this.gridY, 'item_bone'); + const lootItem = (this.type === 'elite_zombie') ? 'item_scrap' : 'item_bone'; + this.scene.interactionSystem.spawnLoot(this.gridX, this.gridY, lootItem); } // Quest Tracking (Kill) diff --git a/src/entities/Player.js b/src/entities/Player.js index 61be896..e201dc4 100644 --- a/src/entities/Player.js +++ b/src/entities/Player.js @@ -340,14 +340,56 @@ class Player { const tile = terrainSystem.tiles[targetY][targetX]; let isPassable = true; - if (tile.type.name === 'water') isPassable = false; + // 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', // Voda + 'MINE_WALL', // Rudniški zidovi + 'WALL_EDGE', // Robovi zidov (DODANO) + 'ORE_STONE', // Kamnita ruda (dokler ni izkopana) + 'ORE_IRON', // Železna ruda + 'lava', // Lava (če bo dodana) + 'void' // Praznina + // Opomba: PAVEMENT je WALKABLE (igralec lahko hodi po cesti) + ]; + + const tileName = tile.type.name || tile.type; + if (isPassable && solidTileTypes.includes(tileName)) { + console.log('⛔ Blocked by solid tile:', tileName); + isPassable = false; + } + + // DECORATION COLLISION - Trdni objekti const key = `${targetX},${targetY}`; if (terrainSystem.decorationsMap.has(key)) { const decor = terrainSystem.decorationsMap.get(key); - const solidTypes = ['tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone']; - if (solidTypes.includes(decor.type)) { - console.log('⛔ Blocked by:', decor.type); + // 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); isPassable = false; } } @@ -397,16 +439,18 @@ class Player { updatePosition() { const screenPos = this.iso.toScreen(this.gridX, this.gridY); - this.sprite.setPosition( - screenPos.x + this.offsetX, - screenPos.y + this.offsetY - ); + + // Pixel-perfect positioning + const x = Math.round(screenPos.x + this.offsetX); + const y = Math.round(screenPos.y + this.offsetY); + + this.sprite.setPosition(x, y); const facingRight = !this.sprite.flipX; const handOffset = facingRight ? 10 : -10; this.handSprite.setPosition( - screenPos.x + this.offsetX + handOffset, - screenPos.y + this.offsetY - 15 + Math.round(x + handOffset), + Math.round(y - 15) ); this.updateDepth(); diff --git a/src/game.js b/src/game.js index db5d7ed..ab186c6 100644 --- a/src/game.js +++ b/src/game.js @@ -54,7 +54,10 @@ const config = { roundPixels: true, transparent: false, clearBeforeRender: true, - powerPreference: 'high-performance' + powerPreference: 'high-performance', + // Eksplicitna NEAREST_NEIGHBOR filtracija + mipmapFilter: 'LINEAR_MIPMAP_LINEAR', + batchSize: 4096 }, physics: { default: 'arcade', @@ -70,6 +73,10 @@ const config = { }, input: { gamepad: true + }, + fps: { + target: 60, + forceSetTimeOut: false } }; diff --git a/src/scenes/BootScene.js b/src/scenes/BootScene.js index 592d76a..98627ad 100644 --- a/src/scenes/BootScene.js +++ b/src/scenes/BootScene.js @@ -38,6 +38,7 @@ class BootScene extends Phaser.Scene { create() { console.log('✅ BootScene: Complete!'); + console.log('🎨 Pixel Art Mode: pixelArt=true, roundPixels=true (NEAREST filtering)'); window.gameState.currentScene = 'BootScene'; // Global Constants for Sprites diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index 3bdb694..27d6a0f 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -50,10 +50,57 @@ class GameScene extends Phaser.Scene { // Initial force update to render active tiles before first frame this.terrainSystem.updateCulling(this.cameras.main); - // FAZA 14: Spawn Ruin (Town Project) at fixed location near player - console.log('🏚️ Spawning Ruin & Arena...'); + // INITIALIZE FARM AREA (Starter Zone @ 20,20) + this.initializeFarmWorld(); + + // CITY CONTENT: Ruins, Chests, Spawners + console.log('🏚️ Generating City Content...'); + + // Main Ruins this.terrainSystem.placeStructure(55, 55, 'ruin'); - this.terrainSystem.placeStructure(75, 75, 'arena'); + this.terrainSystem.placeStructure(65, 65, 'ruin'); + this.terrainSystem.placeStructure(75, 75, 'ruin'); + this.terrainSystem.placeStructure(60, 70, 'ruin'); + this.terrainSystem.placeStructure(70, 60, 'ruin'); + + // Arena (Boss battle area) + this.terrainSystem.placeStructure(75, 55, 'arena'); + + // Treasure Chests (scattered in ruins) + this.terrainSystem.placeStructure(56, 56, 'chest'); + this.terrainSystem.placeStructure(66, 66, 'chest'); + this.terrainSystem.placeStructure(76, 76, 'chest'); + + // Zombie Spawners (in city) + this.terrainSystem.placeStructure(58, 68, 'spawner'); + this.terrainSystem.placeStructure(72, 62, 'spawner'); + + // CESTE (ROADS) - povezava med farmo (20,20) in mestom (65,65) + console.log('🛣️ Building Roads...'); + // Horizontalna cesta od farme + for (let x = 20; x <= 45; x++) { + this.terrainSystem.tiles[22][x].type = 'pavement'; + this.terrainSystem.tiles[22][x].sprite.setTexture('dirt'); // Uporablj dirt kot "cesto" + this.terrainSystem.tiles[22][x].sprite.setTint(0x808080); // Siva barva + } + // Vertikalna cesta do mesta + for (let y = 22; y <= 65; y++) { + this.terrainSystem.tiles[y][45].type = 'pavement'; + this.terrainSystem.tiles[y][45].sprite.setTexture('dirt'); + this.terrainSystem.tiles[y][45].sprite.setTint(0x808080); + } + // Horizontalna cesta do mesta + for (let x = 45; x <= 65; x++) { + this.terrainSystem.tiles[65][x].type = 'pavement'; + this.terrainSystem.tiles[65][x].sprite.setTexture('dirt'); + this.terrainSystem.tiles[65][x].sprite.setTint(0x808080); + } + + // SIGNPOSTS - navigacijske table + console.log('🪧 Placing Signposts...'); + this.terrainSystem.placeStructure(23, 22, 'signpost_city'); // Pri farmi "→ City" + this.terrainSystem.placeStructure(65, 64, 'signpost_farm'); // Pri mestu "← Farm" + this.terrainSystem.placeStructure(45, 40, 'signpost_both'); // Na križišču // Initialize Pathfinding (Worker) console.log('🗺️ Initializing Pathfinding...'); @@ -87,8 +134,17 @@ class GameScene extends Phaser.Scene { this.npcs.push(zombie); } - // Kamera sledi igralcu - this.cameras.main.startFollow(this.player.sprite, true, 1.0, 1.0); + // ELITE ZOMBIES v City območju (65,65 ± 15) + console.log('👹 Spawning ELITE ZOMBIES in City...'); + for (let i = 0; i < 15; i++) { // Veliko elite zombijev! + const randomX = Phaser.Math.Between(50, 80); // City area + const randomY = Phaser.Math.Between(50, 80); + const elite = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, 'elite_zombie'); + this.npcs.push(elite); + } + + // Kamera sledi igralcu z gladko interpolacijo (lerp 0.1) + this.cameras.main.startFollow(this.player.sprite, true, 0.1, 0.1); // Nastavi deadzone (100px border) this.cameras.main.setDeadzone(100, 100); @@ -381,4 +437,46 @@ class GameScene extends Phaser.Scene { loadGame() { if (this.saveSystem) this.saveSystem.loadGame(); } + + initializeFarmWorld() { + console.log('🌾 Initializing Farm Area (Starter Zone)...'); + + const farmX = 20; // Farm center + const farmY = 20; + const farmRadius = 8; + + // 1. Clear farm area (odstrani drevesa in kamne) + for (let x = farmX - farmRadius; x <= farmX + farmRadius; x++) { + for (let y = farmY - farmRadius; y <= farmY + farmRadius; y++) { + if (x >= 0 && x < 100 && y >= 0 && y < 100) { + const key = `${x},${y}`; + // Remove trees and rocks in farm area + if (this.terrainSystem.decorationsMap.has(key)) { + this.terrainSystem.removeDecoration(x, y); + } + // Make it grass or dirt + this.terrainSystem.tiles[y][x].type = { name: 'grass', color: 0x228B22 }; + this.terrainSystem.tiles[y][x].sprite.setTint(0x228B22); + } + } + } + + // 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'); + } + }); + + console.log('✅ Farm Area Initialized at (20,20)'); + } } diff --git a/src/scenes/PreloadScene.js b/src/scenes/PreloadScene.js index 72f91a5..fcc8d5a 100644 --- a/src/scenes/PreloadScene.js +++ b/src/scenes/PreloadScene.js @@ -51,6 +51,7 @@ class PreloadScene extends Phaser.Scene { this.load.image('tree_dead_new', 'assets/tree_dead_new.png'); this.load.image('flowers_new', 'assets/flowers_new.png'); this.load.image('merchant_new', 'assets/merchant_new.png'); + this.load.image('elite_zombie', 'assets/elite_zombie.png'); this.load.image('hill_sprite', 'assets/hill_sprite.png'); this.load.image('fence', 'assets/fence.png'); this.load.image('gravestone', 'assets/gravestone.png'); @@ -123,6 +124,7 @@ class PreloadScene extends Phaser.Scene { 'rock_2', 'rock_small', 'merchant_new', + 'elite_zombie', 'tree_dead_new', 'flowers_new', 'hill_sprite', diff --git a/src/systems/InteractionSystem.js b/src/systems/InteractionSystem.js index 3c18335..62c9f86 100644 --- a/src/systems/InteractionSystem.js +++ b/src/systems/InteractionSystem.js @@ -125,6 +125,34 @@ class InteractionSystem { } } + // 3.5 Try Opening Chest + if (!isAttack && this.scene.terrainSystem) { + const decorKey = `${gridX},${gridY}`; + const decor = this.scene.terrainSystem.decorationsMap.get(decorKey); + + if (decor && decor.type === 'chest') { + // Open chest and spawn loot! + console.log('📦 Opening treasure chest!'); + + // Random loot + const lootTable = ['item_scrap', 'item_chip', 'item_wood', 'item_stone', 'item_bone']; + const lootCount = Phaser.Math.Between(2, 4); + + for (let i = 0; i < lootCount; i++) { + const randomLoot = Phaser.Utils.Array.GetRandom(lootTable); + if (this.scene.lootSystem) { + const offsetX = Phaser.Math.Between(-1, 1); + const offsetY = Phaser.Math.Between(-1, 1); + this.scene.lootSystem.spawnLoot(gridX + offsetX, gridY + offsetY, randomLoot); + } + } + + // Remove chest + this.scene.terrainSystem.removeDecoration(gridX, gridY); + return; + } + } + // 4. Try Planting Tree (Manual Planting) if (!isAttack && (activeTool === 'tree_sapling' || activeTool === 'item_sapling')) { const tile = this.scene.terrainSystem.getTile(gridX, gridY); diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js index e907a76..5d1063c 100644 --- a/src/systems/TerrainSystem.js +++ b/src/systems/TerrainSystem.js @@ -104,6 +104,7 @@ class TerrainSystem { STONE: { name: 'stone', height: 0.7, color: 0x888888 }, PAVEMENT: { name: 'pavement', height: 0.6, color: 0x777777 }, RUINS: { name: 'ruins', height: 0.6, color: 0x555555 }, + WALL_EDGE: { name: 'WALL_EDGE', height: 0.8, color: 0x505050, solid: true }, // OBZIDJE PATH: { name: 'path', height: -1, color: 0xc2b280 }, FARMLAND: { name: 'farmland', height: -1, color: 0x5c4033 }, // MINE TYPES @@ -222,11 +223,26 @@ class TerrainSystem { if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) { terrainType = this.terrainTypes.DIRT; } + // CITY AREA - 15x15 območje z OBZIDJEM if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE && y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) { - terrainType = this.terrainTypes.PAVEMENT; - if (Math.random() < 0.2) { - terrainType = this.terrainTypes.RUINS; + + // Preverimo, ali smo na ROBOVIH (Obzidje) + const isEdge = (x === CITY_START_X || + x === CITY_START_X + CITY_SIZE - 1 || + y === CITY_START_Y || + y === CITY_START_Y + CITY_SIZE - 1); + + if (isEdge) { + // OBZIDJE - trdno, igralec ne more čez + terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true }; + } else { + // NOTRANJOST MESTA - tlakovci (pavement) + terrainType = this.terrainTypes.PAVEMENT; + // Naključne ruševine v mestu + if (Math.random() < 0.15) { + terrainType = this.terrainTypes.RUINS; + } } } @@ -234,7 +250,8 @@ class TerrainSystem { type: terrainType.name, texture: terrainType.name, hasDecoration: false, - hasCrop: false + hasCrop: false, + solid: terrainType.solid || false // Inherits from terrain type }; // Place Trees dynamically during generation @@ -712,4 +729,45 @@ class TerrainSystem { } } } + + placeStructure(x, y, type) { + // Generate textures if needed + if (type === 'chest' && !this.scene.textures.exists('chest')) { + TextureGenerator.createChestSprite(this.scene, 'chest'); + } + if (type === 'spawner' && !this.scene.textures.exists('spawner')) { + TextureGenerator.createSpawnerSprite(this.scene, 'spawner'); + } + if (type === 'ruin' && !this.scene.textures.exists('ruin')) { + TextureGenerator.createStructureSprite(this.scene, 'ruin', 'ruin'); + } + if (type === 'arena' && !this.scene.exists('arena')) { + // Arena uses ruin texture for now + TextureGenerator.createStructureSprite(this.scene, 'arena', 'ruin'); + } + if (type.startsWith('signpost')) { + const textMap = { 'signpost_city': '→', 'signpost_farm': '←', 'signpost_both': '⇅' }; + const text = textMap[type] || '?'; + if (!this.scene.textures.exists(type)) { + TextureGenerator.createSignpostSprite(this.scene, type, text); + } + } + + // Place as decoration + this.addDecoration(x, y, type); + console.log(`🏛️ Place ${type} at (${x}, ${y})`); + } + + setSolid(x, y, isSolid) { + if (x >= 0 && x < this.width && y >= 0 && y < this.height) { + this.tiles[y][x].solid = isSolid; + } + } + + isSolid(x, y) { + if (x >= 0 && x < this.width && y >= 0 && y < this.height) { + return this.tiles[y][x].solid || false; + } + return true; // Out of bounds = solid + } } diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js index bfedb60..c10bcbd 100644 --- a/src/utils/TextureGenerator.js +++ b/src/utils/TextureGenerator.js @@ -53,13 +53,24 @@ class TextureGenerator { ctx.clearRect(0, 0, 32, 32); - ctx.fillStyle = (type === 'zombie') ? '#556B2F' : '#DEB887'; // OliveDrab or Burlywood - ctx.fillRect(8, 6, 16, 26); // Body + // Elite Zombie: Red body, glowing pink eyes + if (type === 'elite_zombie') { + ctx.fillStyle = '#8B0000'; // DarkRed + ctx.fillRect(8, 6, 16, 26); - // Eyes (Red for zombie) - ctx.fillStyle = (type === 'zombie') ? '#FF0000' : '#000000'; - ctx.fillRect(10, 10, 4, 4); - ctx.fillRect(20, 10, 4, 4); + // Glowing Eyes + ctx.fillStyle = '#FF1493'; // Deep Pink (glow) + ctx.fillRect(10, 10, 4, 4); + ctx.fillRect(20, 10, 4, 4); + } else { + ctx.fillStyle = (type === 'zombie') ? '#556B2F' : '#DEB887'; + ctx.fillRect(8, 6, 16, 26); + + // Eyes (Red for zombie) + ctx.fillStyle = (type === 'zombie') ? '#FF0000' : '#000000'; + ctx.fillRect(10, 10, 4, 4); + ctx.fillRect(20, 10, 4, 4); + } canvas.refresh(); } @@ -300,6 +311,78 @@ class TextureGenerator { canvas.refresh(); } + static createChestSprite(scene, key = 'chest') { + if (scene.textures.exists(key)) return; + const canvas = scene.textures.createCanvas(key, 32, 32); + const ctx = canvas.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Chest body (brown) + ctx.fillStyle = '#8B4513'; // SaddleBrown + ctx.fillRect(6, 12, 20, 16); + + // Lock (gold) + ctx.fillStyle = '#FFD700'; + ctx.fillRect(14, 18, 4, 6); + + // Lid + ctx.fillStyle = '#A0522D'; + ctx.fillRect(6, 8, 20, 4); + + canvas.refresh(); + } + + static createSpawnerSprite(scene, key = 'spawner') { + if (scene.textures.exists(key)) return; + const canvas = scene.textures.createCanvas(key, 32, 32); + const ctx = canvas.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Dark portal/spawner + ctx.fillStyle = '#2F4F4F'; // DarkSlateGray + ctx.fillRect(8, 8, 16, 16); + + // Red glow + ctx.fillStyle = '#8B0000'; + ctx.fillRect(10, 10, 12, 12); + + // Center black hole + ctx.fillStyle = '#000000'; + ctx.beginPath(); + ctx.arc(16, 16, 4, 0, Math.PI * 2); + ctx.fill(); + + canvas.refresh(); + } + + static createSignpostSprite(scene, key = 'signpost', text = '→') { + if (scene.textures.exists(key)) return; + const canvas = scene.textures.createCanvas(key, 32, 32); + const ctx = canvas.getContext(); + ctx.clearRect(0, 0, 32, 32); + + // Wooden post (brown) + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 8, 4, 24); + + // Sign board + ctx.fillStyle = '#D2691E'; + ctx.fillRect(6, 10, 20, 12); + + // Border + ctx.strokeStyle = '#000'; + ctx.lineWidth = 1; + ctx.strokeRect(6, 10, 20, 12); + + // Text + ctx.fillStyle = '#000'; + ctx.font = 'bold 14px Courier'; + ctx.textAlign = 'center'; + ctx.fillText(text, 16, 19); + + canvas.refresh(); + } + static createToolSprites(scene) { // Placeholder tool generation if (!scene.textures.exists('item_axe')) { @@ -342,7 +425,9 @@ class TextureGenerator { { name: 'stone', color: '#808080' }, // Siva { name: 'seeds', color: '#90EE90' }, // Svetlo zelena { name: 'wheat', color: '#FFD700' }, // Zlata - { name: 'item_bone', color: '#F5F5DC' } // Beige + { name: 'item_bone', color: '#F5F5DC' }, // Beige + { name: 'item_scrap', color: '#B87333' }, // Copper/Bronze (kovinski kos) + { name: 'item_chip', color: '#00CED1' } // DarkTurquoise (elektronski chip) ]; items.forEach(item => { const it = typeof item === 'string' ? item : item.name;