Files
novafarma/src/systems/TerrainSystem.js
2025-12-08 14:16:24 +01:00

968 lines
37 KiB
JavaScript

// ========================================================
// NOVE GLOBALNE KONSTANTE ZA LOKACIJE
// ========================================================
const FARM_SIZE = 8;
const FARM_CENTER_X = 20; // Lokacija farme na X osi
const FARM_CENTER_Y = 20; // Lokacija farme na Y osi
const CITY_SIZE = 15;
const CITY_START_X = 65; // Desni del mape (npr. med 65 in 80)
const CITY_START_Y = 65;
// ========================================================
// NOVE KONSTANTE ZA RUDNIK IN RUDE
// ========================================================
const TILE_STONE_ORE = 82; // ID za navadni kamen (Ore Tile)
const TILE_IRON_ORE = 83; // ID za železovo rudo
const TILE_PAVEMENT = 16; // ID za prehodno ploščico (tla rudnika)
const TILE_MINE_WALL = 81; // ID za zid rudnika (Solid/Kolizija)
// ID-ji Virov
const ITEM_STONE = 20; // ID za kamen, ki ga igralec dobi
const ITEM_IRON = 21; // ID za železo
const TREE_DENSITY_THRESHOLD = 0.45; // 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) {
this.scene = scene;
this.width = width;
this.height = height;
this.iso = new IsometricUtils(48, 24);
this.noise = new PerlinNoise(Date.now());
this.tiles = [];
this.decorations = [];
this.decorationsMap = new Map();
this.cropsMap = new Map();
this.tileHealthMap = new Map(); // Global register zdravja ploščic
this.visibleTiles = new Map();
this.visibleDecorations = new Map();
this.visibleCrops = new Map();
this.tilePool = {
active: [],
inactive: [],
get: () => {
if (this.tilePool.inactive.length > 0) {
const s = this.tilePool.inactive.pop();
s.setVisible(true);
return s;
}
const s = this.scene.add.sprite(0, 0, 'dirt');
s.setOrigin(0.5, 0.5);
return s;
},
release: (sprite) => {
sprite.setVisible(false);
this.tilePool.inactive.push(sprite);
}
};
this.decorationPool = {
active: [],
inactive: [],
get: () => {
if (this.decorationPool.inactive.length > 0) {
const s = this.decorationPool.inactive.pop();
s.setVisible(true);
s.clearTint();
return s;
}
return this.scene.add.sprite(0, 0, 'tree');
},
release: (sprite) => {
sprite.setVisible(false);
this.decorationPool.inactive.push(sprite);
}
};
this.cropPool = {
active: [],
inactive: [],
get: () => {
if (this.cropPool.inactive.length > 0) {
const s = this.cropPool.inactive.pop();
s.setVisible(true);
return s;
}
return this.scene.add.sprite(0, 0, 'crop_stage_1');
},
release: (sprite) => {
sprite.setVisible(false);
this.cropPool.inactive.push(sprite);
}
};
this.terrainTypes = {
WATER: { name: 'water', height: 0, color: 0x4444ff },
SAND: { name: 'sand', height: 0.2, color: 0xdddd44 },
GRASS_FULL: { name: 'grass_full', height: 0.35, color: 0x44aa44 },
GRASS_TOP: { name: 'grass_top', height: 0.45, color: 0x66cc66 },
DIRT: { name: 'dirt', height: 0.5, color: 0x8b4513 },
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
MINE_FLOOR: { name: 'mine_floor', height: 0, color: 0x333333, id: TILE_PAVEMENT },
MINE_WALL: { name: 'mine_wall', height: 1, color: 0x1a1a1a, id: TILE_MINE_WALL },
ORE_STONE: { name: 'ore_stone', height: 0.5, color: 0x555555, id: TILE_STONE_ORE },
ORE_IRON: { name: 'ore_iron', height: 0.5, color: 0x884444, id: TILE_IRON_ORE }
};
this.offsetX = 0;
this.offsetY = 0;
this.generatedChunks = new Set();
this.chunkSize = 10;
// Init tiles array with NULLs
this.tiles = Array.from({ length: this.height }, () => Array(this.width).fill(null));
}
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 });
// 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;
// 1. STRANICE (Faces)
// Left Face
const cLeft = 0x8B4513;
graphics.fillStyle(cLeft);
graphics.beginPath();
graphics.moveTo(midX, bottomY);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(xs, midY + depth);
graphics.lineTo(xs, midY);
graphics.closePath();
graphics.fill();
graphics.lineStyle(2, cLeft); // Overdraw
graphics.strokePath();
// Right Face
const cRight = 0x6B3410;
graphics.fillStyle(cRight);
graphics.beginPath();
graphics.moveTo(xe, midY);
graphics.lineTo(xe, midY + depth);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(midX, bottomY);
graphics.closePath();
graphics.fill();
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 = 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 = xs + 8 + Math.random() * 30;
const ry = topY + 4 + Math.random() * 16;
graphics.fillRect(rx, ry, 3, 3);
}
}
if (type.name.includes('pavement')) {
graphics.lineStyle(1, 0x555555, 0.5);
graphics.beginPath();
graphics.moveTo(xs + 12, midY + 6);
graphics.lineTo(xs + 36, midY - 6);
graphics.strokePath();
}
graphics.generateTexture(type.name, tileWidth + P * 2, tileHeight + P * 2);
graphics.destroy();
});
}
generate() {
this.createTileTextures();
console.log('🌍 Initializing World (Zone Streaming Mode)...');
// Generate ONLY the starting area (Farm)
// Farm is at 20,20. Let's load 3x3 chunks around it.
const centerCx = Math.floor(FARM_CENTER_X / this.chunkSize);
const centerCy = Math.floor(FARM_CENTER_Y / this.chunkSize);
for (let cy = centerCy - 2; cy <= centerCy + 2; cy++) {
for (let cx = centerCx - 2; cx <= centerCx + 2; cx++) {
this.generateChunk(cx, cy);
}
}
console.log(`✅ World Init Complete. Loaded ${this.generatedChunks.size} chunks.`);
}
generateChunk(cx, cy) {
const key = `${cx},${cy}`;
if (this.generatedChunks.has(key)) return;
// Bounds check
if (cx < 0 || cy < 0 || cx * this.chunkSize >= this.width || cy * this.chunkSize >= this.height) return;
this.generatedChunks.add(key);
// console.log(`🔄 Streaming Chunk: [${cx}, ${cy}]`);
const startX = cx * this.chunkSize;
const startY = cy * this.chunkSize;
const endX = Math.min(startX + this.chunkSize, this.width);
const endY = Math.min(startY + this.chunkSize, this.height);
const validPositions = []; // Local valid positions for this chunk
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
// --- PER TILE GENERATION LOGIC (Moved from old loop) ---
const nx = x * 0.1;
const ny = y * 0.1;
const elevation = this.noise.noise(nx, ny);
let terrainType = this.terrainTypes.GRASS_FULL;
// Edges of WORLD
if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) {
terrainType = this.terrainTypes.GRASS_FULL;
} else {
if (elevation < -0.6) terrainType = this.terrainTypes.WATER; // Deep Ocean
else if (elevation < -0.4 && Math.random() < 0.2) {
// ISOLATED ISLANDS (Nodes)
// If we are in deep water but get random bump -> Island
terrainType = this.terrainTypes.SAND;
}
else if (elevation > 0.1) terrainType = this.terrainTypes.SAND;
else if (elevation > 0.2) terrainType = this.terrainTypes.GRASS_FULL;
else if (elevation > 0.3) terrainType = this.terrainTypes.GRASS_TOP;
else if (elevation > 0.7) terrainType = this.terrainTypes.DIRT;
else if (elevation > 0.85) terrainType = this.terrainTypes.STONE;
}
// Farm Override
if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) {
terrainType = this.terrainTypes.DIRT;
}
// City Override
if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE &&
y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) {
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) {
terrainType = { name: 'WALL_EDGE', color: 0x505050, solid: true };
} else {
terrainType = this.terrainTypes.PAVEMENT;
if (Math.random() < 0.15) {
terrainType = this.terrainTypes.RUINS;
}
}
}
// Create Tile Data
this.tiles[y][x] = {
type: terrainType.name,
texture: terrainType.name,
hasDecoration: false,
hasCrop: false,
solid: terrainType.solid || false
};
// Track valid positions for decorations
if (terrainType.name !== 'water' && terrainType.name !== 'sand' && terrainType.name !== 'stone' && !terrainType.solid) {
// Exclude Farm/City from random decor logic
const isFarm = Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
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) {
validPositions.push({ x, y });
}
}
// Direct Placement Calls (Trees/Rocks) - legacy method support
// this.placeTree(x, y, terrainType.name); // Optional: keep this if preferred over random batch
}
}
// --- CHUNK DECORATION PASS ---
// Instead of global counts, we use probability/density per chunk
// 10x10 = 100 tiles.
// Approx density: 0.2 trees per tile = 20 trees per chunk.
// 1. Random Decorations
validPositions.forEach(pos => {
// Trees
if (Math.random() < 0.05) { // 5% chance per valid tile
this.placeTree(pos.x, pos.y, 'grass'); // force check inside
}
// Rocks
if (Math.random() < 0.02) {
this.placeRock(pos.x, pos.y, 'grass');
}
// Flowers
if (Math.random() < 0.05) {
const flowers = ['flower_red', 'flower_yellow', 'flower_blue'];
const flowerType = flowers[Math.floor(Math.random() * flowers.length)];
this.addDecoration(pos.x, pos.y, flowerType);
}
// Path Stones
if (Math.random() < 0.02) {
this.addDecoration(pos.x, pos.y, 'path_stone');
}
});
}
updateChunks(camera) {
// Calculate which chunks are in view
const view = camera.worldView;
const buffer = 100; // Load slightly outside view
const p1 = this.iso.toGrid(view.x - buffer, view.y - buffer);
const p2 = this.iso.toGrid(view.x + view.width + buffer, view.y + view.height + buffer);
const minCx = Math.floor(Math.min(p1.x, p2.x) / this.chunkSize);
const maxCx = Math.ceil(Math.max(p1.x, p2.x) / this.chunkSize);
const minCy = Math.floor(Math.min(p1.y, p2.y) / this.chunkSize);
const maxCy = Math.ceil(Math.max(p1.y, p2.y) / this.chunkSize);
for (let cy = minCy; cy <= maxCy; cy++) {
for (let cx = minCx; cx <= maxCx; cx++) {
this.generateChunk(cx, cy);
}
}
}
// Retained helper methods...
damageDecoration(x, y, amount) {
const key = `${x},${y}`;
const decor = this.decorationsMap.get(key);
if (!decor) return false;
decor.hp -= amount;
if (this.visibleDecorations.has(key)) {
const sprite = this.visibleDecorations.get(key);
sprite.setTint(0xff0000);
this.scene.time.delayedCall(100, () => sprite.clearTint());
this.scene.tweens.add({
targets: sprite,
x: sprite.x + 2,
duration: 50,
yoyo: true,
repeat: 1
});
}
if (decor.hp <= 0) {
this.removeDecoration(x, y);
// Chance to drop Blueprint
if (this.scene.blueprintSystem) {
this.scene.blueprintSystem.tryDropBlueprint(x, y, 'mining');
}
return 'destroyed';
}
return 'hit';
}
removeDecoration(x, y) {
const key = `${x},${y}`;
const decor = this.decorationsMap.get(key);
if (!decor) return;
if (this.visibleDecorations.has(key)) {
const sprite = this.visibleDecorations.get(key);
sprite.setVisible(false);
this.decorationPool.release(sprite);
this.visibleDecorations.delete(key);
}
this.decorationsMap.delete(key);
const index = this.decorations.indexOf(decor);
if (index > -1) this.decorations.splice(index, 1);
if (this.tiles[y] && this.tiles[y][x]) this.tiles[y][x].hasDecoration = false;
return decor.type;
}
init(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
}
setTile(x, y, type) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
this.tiles[y][x].type = type;
}
}
placeTree(x, y, tileType) {
// 1. Safety Checks
if (!tileType || !tileType.includes('grass')) return;
const isFarm = Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 + 2;
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;
// 2. Noise for clustering (Forests)
const noiseVal = this.noise.noise(x * 0.1 + 123.45, y * 0.1 + 678.90);
// 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';
// 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)
if (r < 0.50) type = window.SPRITE_TREE_SAPLING || 'tree_sapling';
else if (r < 0.60) type = window.SPRITE_TREE_DEAD || 'tree_dead_final';
else if (r < 0.65) type = window.SPRITE_TREE_BLUE || 'tree_blue_final';
// Ostalo (35%) je odraslo drevo
}
// 4. Placement
if (shouldPlace) {
this.addDecoration(x, y, type);
}
}
placeRock(x, y, tileType) {
if (!tileType || !tileType.includes('grass') && !tileType.includes('dirt')) return;
const isFarm = Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 + 2;
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;
// 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_asset'; // Default lep kamen
// 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;
}
if (shouldPlace) {
this.addDecoration(x, y, type);
}
}
placeStructure(gridX, gridY, type) {
if (type === 'ruin') {
for (let y = 0; y < 6; y++) {
for (let x = 0; x < 6; x++) {
if (Math.random() > 0.6) this.addDecoration(gridX + x, gridY + y, 'fence');
this.setTile(gridX + x, gridY + y, 'stone');
}
}
}
if (type === 'arena') {
const size = 12;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const tx = gridX + x;
const ty = gridY + y;
this.setTile(tx, ty, 'stone');
if (x === 0 || x === size - 1 || y === 0 || y === size - 1) {
if (!(x === Math.floor(size / 2) && y === size - 1)) {
this.addDecoration(tx, ty, 'fence');
}
}
}
}
this.addDecoration(gridX + 6, gridY + 6, 'gravestone');
}
if (type === 'ruin_room') {
for (let y = 0; y < 5; y++) {
for (let x = 0; x < 5; x++) {
const tx = gridX + x;
const ty = gridY + y;
if (x > 0 && x < 4 && y > 0 && y < 4) {
this.setTile(tx, ty, 'ruins');
}
if (x === 0 || x === 4 || y === 0 || y === 4) {
const isCenter = (x === 2 || y === 2);
if (isCenter && Math.random() > 0.5) continue;
if (Math.random() > 0.3) {
this.addDecoration(tx, ty, 'fence');
} else {
// User rocks in ruins
if (Math.random() > 0.5) {
const rType = Math.random() > 0.5 ? 'rock_1' : 'rock_2';
this.addDecoration(tx, ty, rType);
}
}
}
}
}
}
}
addDecoration(gridX, gridY, type) {
if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) return;
const key = `${gridX},${gridY}`;
if (this.decorationsMap.has(key)) return;
let scale = 1.0;
if (type === 'rock_1' || type === 'rock_2') scale = 1.5; // Povečano (bilo 0.5)
else if (type === 'tree_green_new' || type === 'tree_blue_new' || type === 'tree_dead_new') scale = 0.04;
else if (type === 'flowers_new') scale = 0.02;
else if (type === 'fence') scale = 0.025;
else if (type === 'gravestone') scale = 0.03;
else if (type === 'hill_sprite') scale = 0.025;
else if (type.includes('_final')) scale = 1.0; // New Final Trees
else {
// Old Assets (Low Res)
if (type.includes('tree')) scale = 1.2 + Math.random() * 0.4;
else if (type.includes('rock')) scale = 0.8;
else scale = 1.0;
}
// Calculate Plant Day for Saplings (Growth System)
let plantDay = -1;
if (type.includes('sapling')) {
const w = this.scene.weatherSystem;
plantDay = w ? w.getDayCount() : 1;
// Random init offset for initial generation
if (!w) plantDay = 1 - Math.floor(Math.random() * 2);
}
// Determine if decoration is SOLID (blocking movement)
const typeLower = type.toLowerCase();
// Small decorations are NOT solid (can walk through)
const isSmallDecor = typeLower.includes('flower') ||
typeLower.includes('small_rock') ||
typeLower.includes('path_stone') ||
typeLower.includes('mushroom') ||
typeLower.includes('puddle');
const isSolid = !isSmallDecor && (
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') ||
typeLower.includes('fallen_log') ||
typeLower.includes('furnace') || // WORKSTATION
typeLower.includes('mint') // WORKSTATION
);
const decorData = {
gridX: gridX,
gridY: gridY,
type: type,
id: key,
maxHp: 10,
hp: 10,
scale: scale,
plantDay: plantDay, // Added for Growth System
solid: isSolid // TRDNOST (collision blocking)
};
this.decorations.push(decorData);
this.decorationsMap.set(key, decorData);
if (this.tiles[gridY] && this.tiles[gridY][gridX]) {
this.tiles[gridY][gridX].hasDecoration = true;
}
// Register Workstation
if (typeLower.includes('furnace') || typeLower.includes('mint')) {
if (this.scene.workstationSystem) {
// Determine type exactly
let stationType = 'furnace';
if (typeLower.includes('mint')) stationType = 'mint';
this.scene.workstationSystem.addStation(gridX, gridY, stationType);
}
}
}
setTileType(x, y, typeName) {
if (!this.tiles[y] || !this.tiles[y][x]) return;
this.tiles[y][x].type = typeName;
const key = `${x},${y}`;
if (this.visibleTiles.has(key)) {
const sprite = this.visibleTiles.get(key);
sprite.setTexture(typeName);
}
}
addCrop(x, y, cropData) {
const key = `${x},${y}`;
this.cropsMap.set(key, cropData);
this.tiles[y][x].hasCrop = true;
}
removeCrop(x, y) {
const key = `${x},${y}`;
if (this.cropsMap.has(key)) {
if (this.visibleCrops.has(key)) {
const sprite = this.visibleCrops.get(key);
sprite.setVisible(false);
this.cropPool.release(sprite);
this.visibleCrops.delete(key);
}
this.cropsMap.delete(key);
this.tiles[y][x].hasCrop = false;
}
}
updateCropVisual(x, y, stage) {
const key = `${x},${y}`;
if (this.visibleCrops.has(key)) {
const sprite = this.visibleCrops.get(key);
sprite.setTexture(`crop_stage_${stage}`);
}
}
getTile(x, y) {
if (this.tiles[y] && this.tiles[y][x]) {
return this.tiles[y][x];
}
return null;
}
updateCulling(camera) {
this.updateChunks(camera);
const view = camera.worldView;
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;
const left = view.x - buffer - this.offsetX;
const top = view.y - buffer - this.offsetY;
const right = view.x + view.width + buffer - this.offsetX;
const bottom = view.y + view.height + buffer - this.offsetY;
const p1 = this.iso.toGrid(left, top);
const p2 = this.iso.toGrid(right, top);
const p3 = this.iso.toGrid(left, bottom);
const p4 = this.iso.toGrid(right, bottom);
const minGridX = Math.floor(Math.min(p1.x, p2.x, p3.x, p4.x));
const maxGridX = Math.ceil(Math.max(p1.x, p2.x, p3.x, p4.x));
const minGridY = Math.floor(Math.min(p1.y, p2.y, p3.y, p4.y));
const maxGridY = Math.ceil(Math.max(p1.y, p2.y, p3.y, p4.y));
const startX = Math.max(0, minGridX);
const endX = Math.min(this.width, maxGridX);
const startY = Math.max(0, minGridY);
const endY = Math.min(this.height, maxGridY);
const neededTileKeys = new Set();
const neededDecorKeys = new Set();
const neededCropKeys = new Set();
const voxelOffset = 12;
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
const key = `${x},${y}`;
const tile = this.tiles[y][x];
if (tile) {
neededTileKeys.add(key);
if (!this.visibleTiles.has(key)) {
const sprite = this.tilePool.get();
// Use water texture with animation support
if (tile.type === 'water') {
// Check if water frames exist
if (this.scene.textures.exists('water_frame_0')) {
sprite.setTexture('water_frame_0');
// Mark sprite for animation
sprite.isWater = true;
sprite.waterFrame = 0;
} else {
sprite.setTexture('water');
}
} else {
sprite.setTexture(tile.type);
}
const screenPos = this.iso.toScreen(x, y);
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);
}
}
const decor = this.decorationsMap.get(key);
if (decor) {
neededDecorKeys.add(key);
if (!this.visibleDecorations.has(key)) {
const sprite = this.decorationPool.get();
const screenPos = this.iso.toScreen(x, y);
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);
} else {
// DREVESA: Origin na dno, da Y-sort deluje
sprite.setOrigin(0.5, 1.0);
}
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1.0);
if (decor.alpha !== undefined) {
sprite.setAlpha(decor.alpha);
}
// Layer Objects
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS));
this.visibleDecorations.set(key, sprite);
}
}
const crop = this.cropsMap.get(key);
if (crop) {
neededCropKeys.add(key);
if (!this.visibleCrops.has(key)) {
const sprite = this.cropPool.get();
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY - voxelOffset));
const cropType = crop.type || 'wheat';
// Če je wheat, uporabimo 'crop_stage_' za nazaj združljivost s TextureGeneratorjem?
// TextureGenerator dela 'crop_stage_X'.
// Če dodam 'wheat_stage_X', moram posodobiti TextureGenerator.
// Za zdaj:
let textureKey = `crop_stage_${crop.stage}`;
if (cropType === 'corn') textureKey = `corn_stage_${crop.stage}`;
if (cropType === 'wheat' && this.scene.textures.exists('wheat_stage_1')) textureKey = `wheat_stage_${crop.stage}`;
sprite.setTexture(textureKey);
sprite.setOrigin(0.5, 1);
// Layer Objects (da igralec hodi okoli njih)
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS));
this.visibleCrops.set(key, sprite);
}
}
}
}
for (const [key, sprite] of this.visibleTiles) {
if (!neededTileKeys.has(key)) {
sprite.setVisible(false);
this.tilePool.release(sprite);
this.visibleTiles.delete(key);
}
}
for (const [key, sprite] of this.visibleDecorations) {
if (!neededDecorKeys.has(key)) {
sprite.setVisible(false);
this.decorationPool.release(sprite);
this.visibleDecorations.delete(key);
}
}
for (const [key, sprite] of this.visibleCrops) {
if (!neededCropKeys.has(key)) {
sprite.setVisible(false);
this.cropPool.release(sprite);
this.visibleCrops.delete(key);
}
}
}
getTile(x, y) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
return this.tiles[y][x];
}
return null;
}
update(delta) {
// Water animation (250ms per frame = 4 FPS)
this.waterAnimTimer = (this.waterAnimTimer || 0) + delta;
if (this.waterAnimTimer > 250) {
this.waterAnimTimer = 0;
this.waterCurrentFrame = ((this.waterCurrentFrame || 0) + 1) % 4;
// Update all water tiles
for (const [key, sprite] of this.visibleTiles) {
if (sprite.isWater) {
sprite.setTexture(`water_frame_${this.waterCurrentFrame}`);
}
}
}
this.growthTimer = (this.growthTimer || 0) + delta;
if (this.growthTimer < 5000) return;
this.growthTimer = 0;
const weather = this.scene.weatherSystem;
if (!weather) return;
const currentDay = weather.getDayCount();
const healthyType = window.SPRITE_TREE_HEALTHY || 'tree_green_final';
for (const decor of this.decorationsMap.values()) {
if (decor.type.includes('sapling')) {
if (decor.plantDay !== undefined && decor.plantDay > -100 && (currentDay - decor.plantDay >= 3)) {
decor.type = healthyType;
decor.scale = 1.0;
decor.hp = 10;
decor.plantDay = -1;
const key = decor.id;
if (this.visibleDecorations.has(key)) {
const sprite = this.visibleDecorations.get(key);
sprite.setTexture(decor.type);
sprite.setScale(decor.scale);
this.scene.tweens.add({
targets: sprite,
scaleY: { from: 0.1, to: decor.scale },
duration: 800,
ease: 'Bounce.out'
});
console.log(`🌳 Tree grew on Day ${currentDay}`);
}
}
}
}
}
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
}
}