različne velikosti dreves

This commit is contained in:
2025-12-07 03:40:50 +01:00
parent 9eb57ed117
commit 521468c797
8 changed files with 323 additions and 109 deletions

View File

@@ -52,7 +52,7 @@ class NPC {
texKey texKey
); );
this.sprite.setOrigin(0.5, 1); this.sprite.setOrigin(0.5, 1);
this.sprite.setScale(0.2); // Mali, detajlni sprite this.sprite.setScale(0.3); // Optimized for visibility
// Depth sorting // Depth sorting
this.updateDepth(); this.updateDepth();

View File

@@ -48,7 +48,7 @@ class Player {
texKey texKey
); );
this.sprite.setOrigin(0.5, 1); this.sprite.setOrigin(0.5, 1);
this.sprite.setScale(0.2); // Mali, detajlni sprite this.sprite.setScale(0.3); // Optimized for visibility
// Depth sorting // Depth sorting
this.updateDepth(); this.updateDepth();

View File

@@ -67,6 +67,9 @@ class GameScene extends Phaser.Scene {
// Round pixels za crisp pixel art // Round pixels za crisp pixel art
this.cameras.main.roundPixels = true; this.cameras.main.roundPixels = true;
// Zoom out za boljši pogled (85% zoom)
this.cameras.main.setZoom(0.85);
// Parallax oblaki // Parallax oblaki
this.createClouds(); this.createClouds();

View File

@@ -165,25 +165,57 @@ class UIScene extends Phaser.Scene {
updateInventory(slots) { updateInventory(slots) {
if (!this.inventorySlots) return; if (!this.inventorySlots) return;
for (let i = 0; i < this.inventorySlots.length; i++) { for (let i = 0; i < this.inventorySlots.length; i++) {
const slotGraphics = this.inventorySlots[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]) { if (slots[i]) {
const { x, y, size } = slotGraphics.userData; const { x, y, size } = slotGraphics.userData;
// Simple representation: Text const type = slots[i].type;
const text = this.add.text(x + size / 2, y + size / 2, const textureKey = `item_${type}`; // e.g., item_axe, item_wood
`${slots[i].type.substring(0, 2)}\n${slots[i].count}`,
{ fontSize: '10px', align: 'center', color: '#ffff00' }
).setOrigin(0.5);
// 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; 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;
}
}
} }
} }

View File

@@ -86,97 +86,166 @@ class InteractionSystem {
} }
} }
// 5. Try damage decoration (fallback) // 5. Try damage decoration
// 5. Try damage or interact decoration
if (this.scene.terrainSystem) {
const id = `${gridPos.x},${gridPos.y}`; const id = `${gridPos.x},${gridPos.y}`;
if (this.scene.terrainSystem.decorationsMap.has(id)) { if (this.scene.terrainSystem.decorationsMap.has(id)) {
const decor = this.scene.terrainSystem.decorationsMap.get(id); const decor = this.scene.terrainSystem.decorationsMap.get(id);
// Calculate Damage based on Tool
let damage = 1; // Default hand damage
// Ruin Interaction - Town Restoration // Tool Logic
if (decor.type === 'ruin' || decor.type === 'ruin_borut') { if (decor.type === 'tree' && activeTool === 'axe') {
// Check if near damage = 3; // Axe destroys tree fast
if (dist > 2.5) { } else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') {
console.log('Ruin too far.'); damage = 3; // Pickaxe destroys stone fast
}
// Apply damage
const result = this.scene.terrainSystem.damageDecoration(gridPos.x, gridPos.y, damage);
if (result === 'destroyed') {
// 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()
});
}
} else if (result === 'hit') {
// Play hit sound
if (this.scene.soundManager) this.scene.soundManager.playChop();
}
}
}
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; return;
} }
// Show Project Menu // Get Active Tool
let activeTool = null;
const uiScene = this.scene.scene.get('UIScene'); const uiScene = this.scene.scene.get('UIScene');
if (uiScene) { const invSys = this.scene.inventorySystem;
// 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') { if (uiScene && invSys) {
ruinName = "Merchant House"; const selectedIdx = uiScene.selectedSlot;
req = { reqWood: 50, reqStone: 30, reqGold: 30 }; const slotData = invSys.slots[selectedIdx];
if (slotData) activeTool = slotData.type;
} }
uiScene.showProjectMenu(req, () => { // REUSE LOGIC
// On Contribute Logic const id = `${gridX},${gridY}`;
if (invSys) { if (this.scene.terrainSystem.decorationsMap.has(id)) {
const hasWood = invSys.hasItem('wood', req.reqWood); const decor = this.scene.terrainSystem.decorationsMap.get(id);
const hasStone = invSys.hasItem('stone', req.reqStone || 0);
const hasGold = invSys.hasItem('gold', req.reqGold || 0);
// Check all requirements // Calculate Damage based on Tool
if (hasWood && hasStone && hasGold) { let damage = 1; // Default hand damage
// Consume materials
invSys.removeItem('wood', req.reqWood);
invSys.removeItem('stone', req.reqStone);
invSys.removeItem('gold', req.reqGold);
invSys.updateUI();
console.log(`🏗️ Restoring ${ruinName}...`); // Tool Logic
if (decor.type === 'tree' && activeTool === 'axe') {
// Transform Ruin -> House damage = 3; // Axe destroys tree fast
this.scene.terrainSystem.removeDecoration(gridPos.x, gridPos.y); } else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') {
this.scene.terrainSystem.placeStructure(gridPos.x, gridPos.y, 'house'); damage = 3; // Pickaxe destroys stone fast
// 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`); // Apply damage
const result = this.scene.terrainSystem.damageDecoration(gridX, gridY, damage);
// 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
}
}
const result = this.scene.terrainSystem.damageDecoration(gridPos.x, gridPos.y, 1);
if (result === 'destroyed') { if (result === 'destroyed') {
// Play chop sound // Play proper sound
if (decor.type === 'tree') {
if (this.scene.soundManager) this.scene.soundManager.playChop(); 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()
});
}
// Spawn loot
this.spawnLoot(gridPos.x, gridPos.y, 'wood');
} else if (result === 'hit') { } else if (result === 'hit') {
// Play hit sound
if (this.scene.soundManager) this.scene.soundManager.playChop(); if (this.scene.soundManager) this.scene.soundManager.playChop();
} }
} }

View File

@@ -7,11 +7,17 @@ class InventorySystem {
this.slots = new Array(9).fill(null); this.slots = new Array(9).fill(null);
this.maxStack = 99; 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('hoe', 1);
this.addItem('seeds', 10); this.addItem('seeds', 5); // Zmanjšano število semen
this.addItem('wood', 100); // For restoration // Removed default wood/stone so player has to gather them
this.addItem('stone', 100); // For restoration
this.gold = 0; this.gold = 0;
} }

View File

@@ -61,7 +61,6 @@ class TerrainSystem {
} }
); );
// Tipi terena z threshold vrednostmi + Y-LAYER STACKING // Tipi terena z threshold vrednostmi + Y-LAYER STACKING
this.terrainTypes = { this.terrainTypes = {
WATER: { threshold: 0.3, color: 0x2166aa, name: 'water', texture: 'tile_water', yLayer: -1 }, 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 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 }, 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 } FARMLAND: { threshold: 999, color: 0x4a3c2a, name: 'farmland', texture: 'tile_farmland', yLayer: 0 }
}; };
} }
@@ -115,7 +115,7 @@ class TerrainSystem {
const tileW = this.iso.tileWidth; const tileW = this.iso.tileWidth;
const tileH = this.iso.tileHeight; 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 }); const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
@@ -197,6 +197,14 @@ class TerrainSystem {
const py = Math.random() * tileH; const py = Math.random() * tileH;
graphics.fillRect(px, py, 2, 1); 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 // 5. Crisp black outline for block definition
@@ -294,6 +302,16 @@ class TerrainSystem {
// Get terrain type based on BOTH noise and elevation (Y-layer) // Get terrain type based on BOTH noise and elevation (Y-layer)
let terrainType = this.getTerrainTypeByElevation(noiseValue, elevation); 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 === // === FLOATING ISLAND EDGE ===
const edgeDistance = 2; // Tiles from edge (tighter border) const edgeDistance = 2; // Tiles from edge (tighter border)
const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance || const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance ||
@@ -333,20 +351,20 @@ class TerrainSystem {
let decorType = null; let decorType = null;
let maxHp = 1; let maxHp = 1;
if (terrainType.name === 'grass') { if (terrainType.name.includes('grass')) { // Check ANY grass type
const rand = Math.random(); const rand = Math.random();
// Na hribih več kamnov // Na hribih več kamnov
if (elevation > 0.6 && rand < 0.05) { if (elevation > 0.6 && rand < 0.05) {
decorType = 'bush'; // Kamni (bomo kasneje naredili 'stone' tip) decorType = 'bush'; // Kamni
maxHp = 5; maxHp = 5;
} else if (rand < 0.01) { } else if (rand < 0.025) { // Reduced to 2.5% trees (optimum balance)
decorType = 'tree'; decorType = 'tree';
maxHp = 5; maxHp = 5;
} else if (rand < 0.015) { } else if (rand < 0.03) {
decorType = 'gravestone'; // 💀 Nagrobniki decorType = 'gravestone'; // 💀 Nagrobniki
maxHp = 10; // Težje uničiti maxHp = 10; // Težje uničiti
} else if (rand < 0.1) { } else if (rand < 0.08) {
decorType = 'flower'; decorType = 'flower';
maxHp = 1; maxHp = 1;
} }
@@ -595,7 +613,11 @@ class TerrainSystem {
this.visibleTiles.set(key, sprite); 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) { if (this.tiles[y][x].hasCrop) {
neededCropKeys.add(key); neededCropKeys.add(key);
if (!this.visibleCrops.has(key)) { if (!this.visibleCrops.has(key)) {
@@ -606,7 +628,7 @@ class TerrainSystem {
sprite.setTexture(`crop_stage_${cropData.stage}`); sprite.setTexture(`crop_stage_${cropData.stage}`);
sprite.setPosition( sprite.setPosition(
cropPos.x + this.offsetX, 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); const depth = this.iso.getDepth(x, y);
sprite.setDepth(depth + 1); // Just slightly above tile sprite.setDepth(depth + 1); // Just slightly above tile
@@ -628,9 +650,11 @@ class TerrainSystem {
const decorPos = this.iso.toScreen(x, y); const decorPos = this.iso.toScreen(x, y);
const sprite = this.decorationPool.get(); const sprite = this.decorationPool.get();
sprite.setTexture(decor.type); sprite.setTexture(decor.type);
// Apply same elevation offset as tile
sprite.setPosition( sprite.setPosition(
decorPos.x + this.offsetX, 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); const depth = this.iso.getDepth(x, y);
@@ -638,6 +662,20 @@ class TerrainSystem {
else sprite.setDepth(depth + 1000); // Taller objects update depth else sprite.setDepth(depth + 1000); // Taller objects update depth
sprite.flipX = (x + y) % 2 === 0; 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); this.visibleDecorations.set(key, sprite);
} }
} }

View File

@@ -765,4 +765,70 @@ class TextureGenerator {
canvas.refresh(); canvas.refresh();
return canvas; 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();
}
}
} }