From 521468c7974afafb7ae315e6c80739efb7e4177c Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Sun, 7 Dec 2025 03:40:50 +0100 Subject: [PATCH] =?UTF-8?q?razli=C4=8Dne=20velikosti=20dreves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/NPC.js | 2 +- src/entities/Player.js | 2 +- src/scenes/GameScene.js | 3 + src/scenes/UIScene.js | 54 +++++-- src/systems/InteractionSystem.js | 233 ++++++++++++++++++++----------- src/systems/InventorySystem.js | 14 +- src/systems/TerrainSystem.js | 58 ++++++-- src/utils/TextureGenerator.js | 66 +++++++++ 8 files changed, 323 insertions(+), 109 deletions(-) diff --git a/src/entities/NPC.js b/src/entities/NPC.js index 14e5fd9..1f7836f 100644 --- a/src/entities/NPC.js +++ b/src/entities/NPC.js @@ -52,7 +52,7 @@ class NPC { texKey ); this.sprite.setOrigin(0.5, 1); - this.sprite.setScale(0.2); // Mali, detajlni sprite + this.sprite.setScale(0.3); // Optimized for visibility // Depth sorting this.updateDepth(); diff --git a/src/entities/Player.js b/src/entities/Player.js index 7e67caf..ca4ddb7 100644 --- a/src/entities/Player.js +++ b/src/entities/Player.js @@ -48,7 +48,7 @@ class Player { texKey ); this.sprite.setOrigin(0.5, 1); - this.sprite.setScale(0.2); // Mali, detajlni sprite + this.sprite.setScale(0.3); // Optimized for visibility // Depth sorting this.updateDepth(); diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index 6f28b67..74df132 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -67,6 +67,9 @@ class GameScene extends Phaser.Scene { // Round pixels za crisp pixel art this.cameras.main.roundPixels = true; + // Zoom out za boljši pogled (85% zoom) + this.cameras.main.setZoom(0.85); + // Parallax oblaki this.createClouds(); diff --git a/src/scenes/UIScene.js b/src/scenes/UIScene.js index 26af533..74c7115 100644 --- a/src/scenes/UIScene.js +++ b/src/scenes/UIScene.js @@ -165,24 +165,56 @@ class UIScene extends Phaser.Scene { updateInventory(slots) { if (!this.inventorySlots) return; + for (let i = 0; i < this.inventorySlots.length; i++) { const slotGraphics = this.inventorySlots[i]; - // Clear previous item info (we stored it in container? No, just graphics) - // Ideally slots should be containers. - // For now, let's just redraw the slot and add text on top. - // To do this cleanly, let's remove old item text/sprites if we track them. - if (slotGraphics.itemText) slotGraphics.itemText.destroy(); + // Clean up old visual elements + if (slotGraphics.itemSprite) { + slotGraphics.itemSprite.destroy(); + slotGraphics.itemSprite = null; + } + if (slotGraphics.itemText) { + slotGraphics.itemText.destroy(); + slotGraphics.itemText = null; + } + if (slotGraphics.countText) { + slotGraphics.countText.destroy(); + slotGraphics.countText = null; + } if (slots[i]) { const { x, y, size } = slotGraphics.userData; - // Simple representation: Text - const text = this.add.text(x + size / 2, y + size / 2, - `${slots[i].type.substring(0, 2)}\n${slots[i].count}`, - { fontSize: '10px', align: 'center', color: '#ffff00' } - ).setOrigin(0.5); + const type = slots[i].type; + const textureKey = `item_${type}`; // e.g., item_axe, item_wood - slotGraphics.itemText = text; + // Check if texture exists + if (this.textures.exists(textureKey)) { + // Draw Sprite + const sprite = this.add.sprite(x + size / 2, y + size / 2, textureKey); + + // Scale to fit slot (32px slot, maybe 24px icon) + const scale = (size - 8) / Math.max(sprite.width, sprite.height); + sprite.setScale(scale); + + slotGraphics.itemSprite = sprite; + } else { + // Fallback Text + const text = this.add.text(x + size / 2, y + size / 2, + type.substring(0, 2).toUpperCase(), + { fontSize: '12px', align: 'center', color: '#ffff00', stroke: '#000', strokeThickness: 2 } + ).setOrigin(0.5); + slotGraphics.itemText = text; + } + + // Draw Count (if > 1) + if (slots[i].count > 1) { + const countText = this.add.text(x + size - 2, y + size - 2, + slots[i].count.toString(), + { fontSize: '10px', align: 'right', color: '#ffffff', stroke: '#000', strokeThickness: 2 } + ).setOrigin(1, 1); + slotGraphics.countText = countText; + } } } } diff --git a/src/systems/InteractionSystem.js b/src/systems/InteractionSystem.js index cfbc23c..499a4e9 100644 --- a/src/systems/InteractionSystem.js +++ b/src/systems/InteractionSystem.js @@ -86,95 +86,71 @@ class InteractionSystem { } } - // 5. Try damage decoration (fallback) - // 5. Try damage or interact decoration - if (this.scene.terrainSystem) { - const id = `${gridPos.x},${gridPos.y}`; - if (this.scene.terrainSystem.decorationsMap.has(id)) { - const decor = this.scene.terrainSystem.decorationsMap.get(id); + // 5. Try damage decoration + const id = `${gridPos.x},${gridPos.y}`; + if (this.scene.terrainSystem.decorationsMap.has(id)) { + const decor = this.scene.terrainSystem.decorationsMap.get(id); + // Calculate Damage based on Tool + let damage = 1; // Default hand damage - // Ruin Interaction - Town Restoration - if (decor.type === 'ruin' || decor.type === 'ruin_borut') { - // Check if near - if (dist > 2.5) { - console.log('Ruin too far.'); - return; - } - - // Show Project Menu - const uiScene = this.scene.scene.get('UIScene'); - if (uiScene) { - // Define requirements based on ruin type - let req = { reqWood: 100, reqStone: 50, reqGold: 50 }; - let ruinName = "Borut's Smithy"; // Default - let npcType = 'merchant'; - - if (decor.type === 'ruin') { - ruinName = "Merchant House"; - req = { reqWood: 50, reqStone: 30, reqGold: 30 }; - } - - uiScene.showProjectMenu(req, () => { - // On Contribute Logic - if (invSys) { - const hasWood = invSys.hasItem('wood', req.reqWood); - const hasStone = invSys.hasItem('stone', req.reqStone || 0); - const hasGold = invSys.hasItem('gold', req.reqGold || 0); - - // Check all requirements - if (hasWood && hasStone && hasGold) { - // Consume materials - invSys.removeItem('wood', req.reqWood); - invSys.removeItem('stone', req.reqStone); - invSys.removeItem('gold', req.reqGold); - invSys.updateUI(); - - console.log(`🏗️ Restoring ${ruinName}...`); - - // Transform Ruin -> House - this.scene.terrainSystem.removeDecoration(gridPos.x, gridPos.y); - this.scene.terrainSystem.placeStructure(gridPos.x, gridPos.y, 'house'); - - // Spawn NPC nearby - const npc = new NPC(this.scene, gridPos.x + 1, gridPos.y + 1, - this.scene.terrainOffsetX, this.scene.terrainOffsetY, npcType); - this.scene.npcs.push(npc); - - // Increase friendship (hearts) - if (this.scene.statsSystem) { - this.scene.statsSystem.addFriendship(npcType, 10); // +10 hearts - } - - console.log(`✅ ${ruinName} Restored! +10 ❤️ Friendship`); - - // Play build sound - if (this.scene.soundManager) this.scene.soundManager.playBuild(); - } else { - // Not enough materials - const missing = []; - if (!hasWood) missing.push(`${req.reqWood} Wood`); - if (!hasStone) missing.push(`${req.reqStone} Stone`); - if (!hasGold) missing.push(`${req.reqGold} Gold`); - - console.log(`❌ Not enough materials! Need: ${missing.join(', ')}`); - alert(`Potrebuješ še: ${missing.join(', ')} da obnoviš ${ruinName}.`); - } - } - }); - } - return; // Don't damage it - } + // Tool Logic + if (decor.type === 'tree' && activeTool === 'axe') { + damage = 3; // Axe destroys tree fast + } else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') { + damage = 3; // Pickaxe destroys stone fast } - const result = this.scene.terrainSystem.damageDecoration(gridPos.x, gridPos.y, 1); + // Apply damage + const result = this.scene.terrainSystem.damageDecoration(gridPos.x, gridPos.y, damage); if (result === 'destroyed') { - // Play chop sound - if (this.scene.soundManager) this.scene.soundManager.playChop(); + // Play proper sound + if (decor.type === 'tree') { + if (this.scene.soundManager) this.scene.soundManager.playChop(); + } else { + // Play stone break sound (using chop for now or generic hit) + if (this.scene.soundManager) this.scene.soundManager.playChop(); + } + + // AUTO-LOOT directly to Inventory + let lootType = 'wood'; // Default + let lootCount = 1; + + if (decor.type === 'tree') { + lootType = 'wood'; + lootCount = 3 + Math.floor(Math.random() * 3); // 3-5 wood + } else if (decor.type === 'bush' || decor.type === 'stone') { + lootType = 'stone'; + lootCount = 2 + Math.floor(Math.random() * 3); // 2-4 stone + } else if (decor.type === 'flower') { + lootType = 'seeds'; // Flowers drop seeds? Or flower item? + lootCount = 1; + } + + console.log(`🎁 Auto-looted: ${lootCount}x ${lootType}`); + + if (invSys) { + invSys.addItem(lootType, lootCount); + + // Show floating text feedback " +3 Wood " + const screenPos = this.iso.toScreen(gridPos.x, gridPos.y); + const txt = this.scene.add.text( + screenPos.x + this.scene.terrainOffsetX, + screenPos.y + this.scene.terrainOffsetY - 30, + `+${lootCount} ${lootType.toUpperCase()}`, + { fontSize: '14px', fill: '#ffff00', stroke: '#000', strokeThickness: 2 } + ); + txt.setOrigin(0.5); + this.scene.tweens.add({ + targets: txt, + y: txt.y - 40, + alpha: 0, + duration: 1000, + onComplete: () => txt.destroy() + }); + } - // Spawn loot - this.spawnLoot(gridPos.x, gridPos.y, 'wood'); } else if (result === 'hit') { // Play hit sound if (this.scene.soundManager) this.scene.soundManager.playChop(); @@ -182,6 +158,99 @@ class InteractionSystem { } } + handleDecorationClick(gridX, gridY) { + if (!this.scene.player) return; + + // Check distance + const playerPos = this.scene.player.getPosition(); + const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, gridX, gridY); + + if (dist > 3.0) { // Slightly increased radius for easier clicking + console.log('Too far:', dist.toFixed(1)); + return; + } + + // Get Active Tool + let activeTool = null; + const uiScene = this.scene.scene.get('UIScene'); + const invSys = this.scene.inventorySystem; + + if (uiScene && invSys) { + const selectedIdx = uiScene.selectedSlot; + const slotData = invSys.slots[selectedIdx]; + if (slotData) activeTool = slotData.type; + } + + // REUSE LOGIC + const id = `${gridX},${gridY}`; + if (this.scene.terrainSystem.decorationsMap.has(id)) { + const decor = this.scene.terrainSystem.decorationsMap.get(id); + + // Calculate Damage based on Tool + let damage = 1; // Default hand damage + + // Tool Logic + if (decor.type === 'tree' && activeTool === 'axe') { + damage = 3; // Axe destroys tree fast + } else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') { + damage = 3; // Pickaxe destroys stone fast + } + + // Apply damage + const result = this.scene.terrainSystem.damageDecoration(gridX, gridY, damage); + + if (result === 'destroyed') { + // Play proper sound + if (decor.type === 'tree') { + if (this.scene.soundManager) this.scene.soundManager.playChop(); + } else { + if (this.scene.soundManager) this.scene.soundManager.playChop(); + } + + // AUTO-LOOT directly to Inventory + let lootType = 'wood'; // Default + let lootCount = 1; + + if (decor.type === 'tree') { + lootType = 'wood'; + lootCount = 3 + Math.floor(Math.random() * 3); // 3-5 wood + } else if (decor.type === 'bush' || decor.type === 'stone') { + lootType = 'stone'; + lootCount = 2 + Math.floor(Math.random() * 3); // 2-4 stone + } else if (decor.type === 'flower') { + lootType = 'seeds'; + lootCount = 1; + } + + console.log(`🎁 Auto-looted: ${lootCount}x ${lootType}`); + + if (invSys) { + invSys.addItem(lootType, lootCount); + + // Show floating text feedback + const screenPos = this.iso.toScreen(gridX, gridY); + const txt = this.scene.add.text( + screenPos.x + this.scene.terrainOffsetX, + screenPos.y + this.scene.terrainOffsetY - 30, + `+${lootCount} ${lootType.toUpperCase()}`, + { fontSize: '14px', fill: '#ffff00', stroke: '#000', strokeThickness: 2 } + ); + txt.setOrigin(0.5); + this.scene.tweens.add({ + targets: txt, + y: txt.y - 40, + alpha: 0, + duration: 1000, + onComplete: () => txt.destroy() + }); + } + + } else if (result === 'hit') { + if (this.scene.soundManager) this.scene.soundManager.playChop(); + } + } + } + spawnLoot(gridX, gridY, type) { console.log(`🎁 Spawning ${type} at ${gridX},${gridY}`); diff --git a/src/systems/InventorySystem.js b/src/systems/InventorySystem.js index e8e941b..e6e60e6 100644 --- a/src/systems/InventorySystem.js +++ b/src/systems/InventorySystem.js @@ -7,11 +7,17 @@ class InventorySystem { this.slots = new Array(9).fill(null); this.maxStack = 99; - // Initial test items + // Generate tool icons if missing + if (typeof TextureGenerator !== 'undefined') { + TextureGenerator.createToolSprites(this.scene); + } + + // Initial items + this.addItem('axe', 1); // 🪓 Sekira + this.addItem('pickaxe', 1); // ⛏️ Kramp this.addItem('hoe', 1); - this.addItem('seeds', 10); - this.addItem('wood', 100); // For restoration - this.addItem('stone', 100); // For restoration + this.addItem('seeds', 5); // Zmanjšano število semen + // Removed default wood/stone so player has to gather them this.gold = 0; } diff --git a/src/systems/TerrainSystem.js b/src/systems/TerrainSystem.js index ddf37cf..0554110 100644 --- a/src/systems/TerrainSystem.js +++ b/src/systems/TerrainSystem.js @@ -61,7 +61,6 @@ class TerrainSystem { } ); - // Tipi terena z threshold vrednostmi + Y-LAYER STACKING this.terrainTypes = { WATER: { threshold: 0.3, color: 0x2166aa, name: 'water', texture: 'tile_water', yLayer: -1 }, @@ -73,6 +72,7 @@ class TerrainSystem { DIRT: { threshold: 0.75, color: 0x8b6f47, name: 'dirt', texture: 'tile_dirt', yLayer: 2 }, // C: Full dirt STONE: { threshold: 1.0, color: 0x7d7d7d, name: 'stone', texture: 'tile_stone', yLayer: 3 }, + PATH: { threshold: 999, color: 0x9b7653, name: 'path', texture: 'tile_path', yLayer: 0 }, // Pot/Road FARMLAND: { threshold: 999, color: 0x4a3c2a, name: 'farmland', texture: 'tile_farmland', yLayer: 0 } }; } @@ -115,7 +115,7 @@ class TerrainSystem { const tileW = this.iso.tileWidth; const tileH = this.iso.tileHeight; - const thickness = 25; // Minecraft-style thickness (increased from 10) + const thickness = 8; // Minimal thickness - nearly 2D const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false }); @@ -197,6 +197,14 @@ class TerrainSystem { const py = Math.random() * tileH; graphics.fillRect(px, py, 2, 1); } + } else if (type.name === 'path') { + // Path texture: Small gravel stones + graphics.fillStyle(0x7a5e42, 0.5); // Darker brown + for (let i = 0; i < 12; i++) { + const px = Math.random() * tileW; + const py = Math.random() * tileH; + graphics.fillRect(px, py, 2, 2); + } } // 5. Crisp black outline for block definition @@ -294,6 +302,16 @@ class TerrainSystem { // Get terrain type based on BOTH noise and elevation (Y-layer) let terrainType = this.getTerrainTypeByElevation(noiseValue, elevation); + // === PATH GENERATION === + // Use a separate noise layer for paths (higher frequency for winding roads) + const pathNoise = this.noise.getNormalized(x, y, 0.08, 2); + + // Create minimal paths - if noise is in a very specific narrow band + // Avoid water (0.3 threshold) and edges + if (pathNoise > 0.48 && pathNoise < 0.52 && noiseValue > 0.35) { + terrainType = this.terrainTypes.PATH; + } + // === FLOATING ISLAND EDGE === const edgeDistance = 2; // Tiles from edge (tighter border) const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance || @@ -333,20 +351,20 @@ class TerrainSystem { let decorType = null; let maxHp = 1; - if (terrainType.name === 'grass') { + if (terrainType.name.includes('grass')) { // Check ANY grass type const rand = Math.random(); // Na hribih več kamnov if (elevation > 0.6 && rand < 0.05) { - decorType = 'bush'; // Kamni (bomo kasneje naredili 'stone' tip) + decorType = 'bush'; // Kamni maxHp = 5; - } else if (rand < 0.01) { + } else if (rand < 0.025) { // Reduced to 2.5% trees (optimum balance) decorType = 'tree'; maxHp = 5; - } else if (rand < 0.015) { + } else if (rand < 0.03) { decorType = 'gravestone'; // 💀 Nagrobniki maxHp = 10; // Težje uničiti - } else if (rand < 0.1) { + } else if (rand < 0.08) { decorType = 'flower'; maxHp = 1; } @@ -595,7 +613,11 @@ class TerrainSystem { this.visibleTiles.set(key, sprite); } - // Crop Logic (render before decor or after? Same layer mostly) + // Elevation effect matching tile logic + const tileData = this.tiles[y][x]; + const elevationOffset = tileData.elevation * -25; + + // Crop Logic if (this.tiles[y][x].hasCrop) { neededCropKeys.add(key); if (!this.visibleCrops.has(key)) { @@ -606,7 +628,7 @@ class TerrainSystem { sprite.setTexture(`crop_stage_${cropData.stage}`); sprite.setPosition( cropPos.x + this.offsetX, - cropPos.y + this.offsetY + this.iso.tileHeight / 2 + cropPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset ); const depth = this.iso.getDepth(x, y); sprite.setDepth(depth + 1); // Just slightly above tile @@ -628,9 +650,11 @@ class TerrainSystem { const decorPos = this.iso.toScreen(x, y); const sprite = this.decorationPool.get(); sprite.setTexture(decor.type); + + // Apply same elevation offset as tile sprite.setPosition( decorPos.x + this.offsetX, - decorPos.y + this.offsetY + this.iso.tileHeight / 2 + decorPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset ); const depth = this.iso.getDepth(x, y); @@ -638,6 +662,20 @@ class TerrainSystem { else sprite.setDepth(depth + 1000); // Taller objects update depth sprite.flipX = (x + y) % 2 === 0; + + // INTERACTIVITY FIX: Allow clicking sprites directly + sprite.setInteractive({ pixelPerfect: true, useHandCursor: true }); + + // Clear old listeners + sprite.off('pointerdown'); + // Add click listener + sprite.on('pointerdown', (pointer) => { + if (this.scene.interactionSystem) { + // Manually trigger interaction logic + this.scene.interactionSystem.handleDecorationClick(x, y); + } + }); + this.visibleDecorations.set(key, sprite); } } diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js index e1d0296..07dc8f3 100644 --- a/src/utils/TextureGenerator.js +++ b/src/utils/TextureGenerator.js @@ -765,4 +765,70 @@ class TextureGenerator { canvas.refresh(); return canvas; } + + // ========== ITEM ICONS ========== + + static createToolSprites(scene) { + // AXE ICON + if (!scene.textures.exists('item_axe')) { + const size = 32; + const canvas = scene.textures.createCanvas('item_axe', size, size); + const ctx = canvas.getContext(); + + ctx.clearRect(0, 0, size, size); + + // Handle + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 10, 4, 18); + + // Blade (Double bit axe style) + ctx.fillStyle = '#C0C0C0'; // Silver + ctx.beginPath(); + ctx.moveTo(16, 12); + ctx.lineTo(6, 6); // Left Top + ctx.lineTo(6, 18); // Left Bottom + ctx.lineTo(16, 14); // Center Bottom + ctx.lineTo(26, 18); // Right Bottom + ctx.lineTo(26, 6); // Right Top + ctx.closePath(); + ctx.fill(); + + // Edge + ctx.strokeStyle = '#FFFFFF'; + ctx.lineWidth = 1; + ctx.stroke(); + + canvas.refresh(); + } + + // PICKAXE ICON + if (!scene.textures.exists('item_pickaxe')) { + const size = 32; + const canvas = scene.textures.createCanvas('item_pickaxe', size, size); + const ctx = canvas.getContext(); + + ctx.clearRect(0, 0, size, size); + + // Handle + ctx.fillStyle = '#8B4513'; + ctx.fillRect(14, 10, 4, 18); + + // Head (Curved) + ctx.fillStyle = '#808080'; // Dark Grey + ctx.beginPath(); + ctx.moveTo(2, 12); // Left Tip + ctx.quadraticCurveTo(16, 4, 30, 12); // Curve to Right Tip + ctx.lineTo(28, 16); // Right Inner + ctx.quadraticCurveTo(16, 8, 4, 16); // Curve to Left Inner + ctx.closePath(); + ctx.fill(); + + // Outline + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 1; + ctx.stroke(); + + canvas.refresh(); + } + } }