popravek zombijo

This commit is contained in:
2025-12-07 13:16:04 +01:00
parent 2404d44ef7
commit 98059a2659
9 changed files with 577 additions and 385 deletions

View File

@@ -25,9 +25,6 @@ class TerrainSystem {
this.lastCullX = -9999;
this.lastCullY = -9999;
// Object Pools
// Tiles will use Blitter, so no Sprite Pool needed for them.
this.blitters = new Map(); // Key: textureKey, Value: Blitter Object
// Object Pools
this.tilePool = new ObjectPool(
() => {
@@ -104,6 +101,23 @@ class TerrainSystem {
return baseType;
}
// Helper za določanje tipa terena glede na noise vrednost
getTerrainType(value) {
if (value < this.terrainTypes.WATER.threshold) return this.terrainTypes.WATER;
if (value < this.terrainTypes.SAND.threshold) return this.terrainTypes.SAND;
if (value < this.terrainTypes.GRASS_FULL.threshold) return this.terrainTypes.GRASS_FULL; // Fallback grass
if (value < this.terrainTypes.GRASS_TOP.threshold) return this.terrainTypes.GRASS_TOP;
if (value < this.terrainTypes.DIRT.threshold) return this.terrainTypes.DIRT;
return this.terrainTypes.STONE;
}
getTile(x, y) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
return this.tiles[y][x];
}
return null;
}
// Generiraj teksture za tiles (da ne uporabljamo Počasnih Graphics objektov)
createTileTextures() {
console.log('🎨 Creating tile textures...');
@@ -178,45 +192,6 @@ class TerrainSystem {
graphics.closePath();
graphics.fillPath();
// 4. Add Minecraft-style texture pattern on top
if (type.name === 'grass_full' || type.name === 'grass_top') {
// Grass texture: Random pixel pattern
graphics.fillStyle(0x4a9d3f, 0.3); // Slightly darker green
for (let i = 0; i < 8; i++) {
const px = Math.random() * tileW;
const py = Math.random() * tileH;
graphics.fillRect(px, py, 2, 2);
}
} else if (type.name === 'dirt') {
// Dirt texture: Darker spots
graphics.fillStyle(0x6d5838, 0.4);
for (let i = 0; i < 6; i++) {
const px = Math.random() * tileW;
const py = Math.random() * tileH;
graphics.fillRect(px, py, 3, 3);
}
} else if (type.name === 'stone') {
// Stone texture: Gray spots
graphics.fillStyle(0x666666, 0.3);
for (let i = 0; i < 10; i++) {
const px = Math.random() * tileW;
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
graphics.lineStyle(1, 0x000000, 0.3);
graphics.strokePath();
// Generate texture
graphics.generateTexture(key, tileW, tileH + thickness);
graphics.destroy();
@@ -227,27 +202,21 @@ class TerrainSystem {
}
createGravestoneSprite() {
// Extract gravestone from objects_pack (approx position in atlas)
// Gravestone appears to be around position row 4, column 4-5 in the pack
const canvas = document.createElement('canvas');
const size = 32; // Approximate sprite size
const size = 32;
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const sourceTexture = this.scene.textures.get('objects_pack');
const sourceImg = sourceTexture.getSourceImage();
// Extract gravestone (cross tombstone) - estimated coords
// Adjust these values based on actual sprite sheet layout
const sourceX = 240; // Approximate X position
const sourceY = 160; // Approximate Y position
ctx.drawImage(sourceImg, sourceX, sourceY, size, size, 0, 0, size, size);
// Create texture
this.scene.textures.addCanvas('gravestone', canvas);
console.log('✅ Gravestone sprite extracted!');
if (this.scene.textures.exists('objects_pack')) {
const sourceTexture = this.scene.textures.get('objects_pack');
const sourceImg = sourceTexture.getSourceImage();
const sourceX = 240;
const sourceY = 160;
ctx.drawImage(sourceImg, sourceX, sourceY, size, size, 0, 0, size, size);
this.scene.textures.addCanvas('gravestone', canvas);
console.log('✅ Gravestone sprite extracted!');
}
}
// Generiraj teren (data only)
@@ -257,90 +226,51 @@ class TerrainSystem {
// Zagotovi teksture
this.createTileTextures();
// DELETED Blitter Init
// Zagotovi decoration teksture - check for custom sprites first
if (!this.scene.textures.exists('flower')) {
TextureGenerator.createFlowerSprite(this.scene, 'flower');
}
// Bush - use custom stone sprite if available
if (!this.scene.textures.exists('flower')) TextureGenerator.createFlowerSprite(this.scene, 'flower');
if (this.scene.textures.exists('stone_sprite')) {
// Use stone_sprite for bushes (rocks)
if (!this.scene.textures.exists('bush')) {
this.scene.textures.addImage('bush', this.scene.textures.get('stone_sprite').getSourceImage());
}
if (!this.scene.textures.exists('bush')) this.scene.textures.addImage('bush', this.scene.textures.get('stone_sprite').getSourceImage());
} else if (!this.scene.textures.exists('bush')) {
TextureGenerator.createBushSprite(this.scene, 'bush');
}
// Tree - use custom tree sprite if available
if (this.scene.textures.exists('tree_sprite')) {
if (!this.scene.textures.exists('tree')) {
this.scene.textures.addImage('tree', this.scene.textures.get('tree_sprite').getSourceImage());
}
if (!this.scene.textures.exists('tree')) this.scene.textures.addImage('tree', this.scene.textures.get('tree_sprite').getSourceImage());
} else if (!this.scene.textures.exists('tree')) {
TextureGenerator.createTreeSprite(this.scene, 'tree');
}
// Gravestone - extract from objects_pack
if (this.scene.textures.exists('objects_pack') && !this.scene.textures.exists('gravestone')) {
this.createGravestoneSprite();
}
// Crop textures
for (let i = 1; i <= 4; i++) {
if (!this.scene.textures.exists(`crop_stage_${i}`))
TextureGenerator.createCropSprite(this.scene, `crop_stage_${i}`, i);
if (!this.scene.textures.exists(`crop_stage_${i}`)) TextureGenerator.createCropSprite(this.scene, `crop_stage_${i}`, i);
}
this.decorationsMap.clear();
this.decorations = [];
this.cropsMap.clear();
// Generiraj tile podatke
for (let y = 0; y < this.height; y++) {
this.tiles[y] = [];
for (let x = 0; x < this.width; x++) {
const noiseValue = this.noise.getNormalized(x, y, 0.05, 4);
// Elevation (druga Perlin noise layer za hribe)
let elevation = this.noise.getNormalized(x, y, 0.03, 3);
// 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 ||
y < edgeDistance || y >= this.height - edgeDistance;
const edgeDistance = 2;
const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance || y < edgeDistance || y >= this.height - edgeDistance;
const isEdge = x === 0 || x === this.width - 1 || y === 0 || y === this.height - 1;
const isEdge = x === 0 || x === this.width - 1 ||
y === 0 || y === this.height - 1;
if (isEdge) terrainType = this.terrainTypes.STONE;
// Override terrain type at edges
if (isEdge) {
terrainType = this.terrainTypes.STONE; // Cliff wall (stone edge)
} else if (isNearEdge) {
// Keep Y-layer system active
}
// Flatten edges (cliff drop-off for floating island effect)
if (isEdge) {
elevation = 0; // Flat cliff wall
} else if (isNearEdge) {
elevation = Math.max(0, elevation - 0.2); // Slight dip near edge
}
if (isEdge) elevation = 0;
else if (isNearEdge) elevation = Math.max(0, elevation - 0.2);
this.tiles[y][x] = {
gridX: x,
@@ -348,45 +278,32 @@ class TerrainSystem {
type: terrainType.name,
texture: terrainType.texture,
height: noiseValue,
elevation: elevation, // 0-1 (0=low, 1=high)
yLayer: terrainType.yLayer, // Y-stacking layer
elevation: elevation,
yLayer: terrainType.yLayer,
hasDecoration: false,
hasCrop: false
};
// Generacija dekoracij (shranimo v data, ne ustvarjamo sprite-ov še)
if (x > 5 && x < this.width - 5 && y > 5 && y < this.height - 5) {
let decorType = null;
let maxHp = 1;
let scale = 1.0; // Default scale
let scale = 1.0;
if (terrainType.name.includes('grass')) { // Check ANY grass type
if (terrainType.name.includes('grass')) {
const rand = Math.random();
// Na hribih več kamnov
if (elevation > 0.6 && rand < 0.05) {
decorType = 'bush'; // Kamni
decorType = 'bush';
maxHp = 5;
} else if (rand < 0.025) { // Reduced to 2.5% trees (optimum balance)
} else if (rand < 0.025) {
decorType = 'tree';
maxHp = 5;
// TREE SIZING LOGIC
// 20% Normal (1.0)
// 60% Medium (0.6-0.8)
// 20% Tiny (0.2)
const sizeRand = Math.random();
if (sizeRand < 0.2) {
scale = 0.25; // 20% Tiny (0.25 visible enough)
} else if (sizeRand < 0.8) {
scale = 0.6 + Math.random() * 0.2; // 60% Scale 0.6-0.8
} else {
scale = 1.0; // 20% Full size
}
if (sizeRand < 0.2) scale = 0.25;
else if (sizeRand < 0.8) scale = 0.6 + Math.random() * 0.2;
else scale = 1.0;
} else if (rand < 0.03) {
decorType = 'gravestone'; // 💀 Nagrobniki
maxHp = 10; // Težje uničiti
decorType = 'gravestone';
maxHp = 10;
} else if (rand < 0.08) {
decorType = 'flower';
maxHp = 1;
@@ -405,37 +322,29 @@ class TerrainSystem {
id: key,
maxHp: maxHp,
hp: maxHp,
scale: scale // Save scale
scale: scale
};
this.decorations.push(decorData);
this.decorationsMap.set(key, decorData);
this.tiles[y][x].hasDecoration = true;
}
}
}
}
console.log('✅ Terrain and decorations data generated!');
console.log('✅ Terrain and decorations generated!');
}
// DAMAGE / INTERACTION LOGIC
damageDecoration(x, y, amount) {
const key = `${x},${y}`;
const decor = this.decorationsMap.get(key);
if (!decor) return false;
decor.hp -= amount;
// Visual feedback (flash red)
if (this.visibleDecorations.has(key)) {
const sprite = this.visibleDecorations.get(key);
sprite.setTint(0xff0000);
this.scene.time.delayedCall(100, () => sprite.clearTint());
// Shake effect?
this.scene.tweens.add({
targets: sprite,
x: sprite.x + 2,
@@ -449,17 +358,14 @@ class TerrainSystem {
this.removeDecoration(x, y);
return 'destroyed';
}
return 'hit';
}
removeDecoration(x, y) {
const key = `${x},${y}`;
const decor = this.decorationsMap.get(key);
if (!decor) return;
// Remove visual
if (this.visibleDecorations.has(key)) {
const sprite = this.visibleDecorations.get(key);
sprite.setVisible(false);
@@ -467,60 +373,38 @@ class TerrainSystem {
this.visibleDecorations.delete(key);
}
// Remove data
this.decorationsMap.delete(key);
// Remove from array (slow but needed for save compat for now)
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;
// Update tile flag
if (this.tiles[y] && this.tiles[y][x]) {
this.tiles[y][x].hasDecoration = false;
}
return decor.type; // Return type for dropping loot
return decor.type;
}
placeStructure(x, y, structureType) {
if (this.decorationsMap.has(`${x},${y}`)) return false;
const decorData = {
gridX: x,
gridY: y,
type: structureType, // 'struct_fence', etc.
type: structureType,
id: `${x},${y}`,
maxHp: 5,
hp: 5
};
// Add to data
this.decorations.push(decorData);
this.decorationsMap.set(decorData.id, decorData);
// Update tile
const tile = this.getTile(x, y);
if (tile) tile.hasDecoration = true;
// Force Visual Update immediately?
// updateCulling will catch it on next frame, but to be safe:
this.lastCullX = -9999; // Force update
this.lastCullX = -9999;
return true;
}
// --- Dynamic Tile Modification ---
setTileType(x, y, typeName) {
if (!this.tiles[y] || !this.tiles[y][x]) return;
const typeDef = Object.values(this.terrainTypes).find(t => t.name === typeName);
if (!typeDef) return;
this.tiles[y][x].type = typeName;
this.tiles[y][x].texture = typeDef.texture;
// Force visual update if visible
const key = `${x},${y}`;
if (this.visibleTiles.has(key)) {
const sprite = this.visibleTiles.get(key);
@@ -532,15 +416,12 @@ class TerrainSystem {
const key = `${x},${y}`;
this.cropsMap.set(key, cropData);
this.tiles[y][x].hasCrop = true;
this.tiles[y][x].hasCrop = true;
// updateCulling loop will pick it up on next frame but we force it
this.lastCullX = -9999;
}
removeCrop(x, y) {
const key = `${x},${y}`;
if (this.cropsMap.has(key)) {
// Remove visual
if (this.visibleCrops.has(key)) {
const sprite = this.visibleCrops.get(key);
sprite.setVisible(false);
@@ -560,35 +441,16 @@ class TerrainSystem {
}
}
// Initialize rendering (called once)
init(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
}
// Update culling (called every frame)
updateCulling(camera) {
// Throttling Optimization - TEMPORARILY DISABLED FOR DEBUG
// const dist = Phaser.Math.Distance.Between(camera.scrollX, camera.scrollY, this.lastCullX, this.lastCullY);
// if (dist < 50) return;
// Debug log once
if (!this.hasLogged) {
console.log('UpdateCulling running. Camera:', camera.scrollX, camera.scrollY);
this.hasLogged = true;
}
this.lastCullX = camera.scrollX;
this.lastCullY = camera.scrollY;
// ... (rest of setup)
// Simple Culling
const view = camera.worldView;
// Optimization: Adjust buffer based on View Distance setting
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') {
buffer = 50;
}
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;
@@ -604,12 +466,6 @@ class TerrainSystem {
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));
// Debug bounds once
if (!this.hasLoggedBounds) {
console.log('Culling Bounds:', minGridX, maxGridX, minGridY, maxGridY);
this.hasLoggedBounds = true;
}
const startX = Math.max(0, minGridX);
const endX = Math.min(this.width, maxGridX);
const startY = Math.max(0, minGridY);
@@ -621,119 +477,68 @@ class TerrainSystem {
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
const key = `${x},${y}`;
neededKeys.add(key);
// TILES
const key = `${x},${y}`;
neededKeys.add(key);
// Tile Logic
if (!this.visibleTiles.has(key)) {
const tilePos = this.iso.toScreen(x, y);
const tileData = this.tiles[y][x];
if (!this.visibleTiles.has(key)) {
const tile = this.tiles[y][x];
const screenPos = this.iso.toScreen(x, y);
const sprite = this.tilePool.get();
// Get from Pool
const sprite = this.tilePool.get();
sprite.setTexture(tileData.texture);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setTexture(tile.texture);
sprite.setDepth(this.iso.getDepth(x, y));
this.visibleTiles.set(key, sprite);
}
// Elevation effect
const elevationOffset = tileData.elevation * -25;
sprite.setPosition(
tilePos.x + this.offsetX,
tilePos.y + this.offsetY + elevationOffset
);
// DECORATIONS
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);
// Senčenje
if (tileData.type.includes('grass')) {
let brightness = 1.0;
if (tileData.elevation > 0.5) {
brightness = 1.0 + (tileData.elevation - 0.5) * 1.0;
} else {
brightness = 0.7 + tileData.elevation * 0.6;
}
sprite.setTint(Phaser.Display.Color.GetColor(
Math.min(255, Math.floor(92 * brightness)),
Math.min(255, Math.floor(184 * brightness)),
Math.min(255, Math.floor(92 * brightness))
));
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
// Fix for house sprite
if (decor.type.includes('house_sprite') || decor.type.includes('market_sprite')) {
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1.0);
} else {
sprite.clearTint();
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1);
}
// FIXED DEPTH FOR DEBUG
sprite.setDepth(-1000);
sprite.setVisible(true);
// Origin adjusted for buildings
if (decor.type.includes('house') || decor.type.includes('market')) {
sprite.setOrigin(0.5, 0.8); // Adjusted origin for buildings
} else {
sprite.setOrigin(0.5, 1);
}
this.visibleTiles.set(key, sprite);
sprite.setDepth(this.iso.getDepth(x, y) + 1); // Above tile
this.visibleDecorations.set(key, sprite);
}
}
// Crop Logic
if (this.tiles[y][x].hasCrop) {
neededCropKeys.add(key);
if (!this.visibleCrops.has(key)) {
const cropData = this.cropsMap.get(key);
if (cropData) {
const cropPos = this.iso.toScreen(x, y);
const tileData = this.tiles[y][x];
const elevationOffset = tileData.elevation * -25;
const sprite = this.cropPool.get();
sprite.setTexture(`crop_stage_${cropData.stage}`);
sprite.setPosition(
cropPos.x + this.offsetX,
cropPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset
);
// Crop depth = Y pos
const depth = this.iso.getDepth(x, y);
sprite.setDepth(depth);
this.visibleCrops.set(key, sprite);
}
}
}
// Decoration Logic
if (this.tiles[y][x].hasDecoration) {
neededDecorKeys.add(key);
if (!this.visibleDecorations.has(key)) {
// Fast lookup from map
const decor = this.decorationsMap.get(key);
const tileData = this.tiles[y][x];
const elevationOffset = tileData.elevation * -25;
if (decor) {
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 + elevationOffset
);
const depth = this.iso.getDepth(x, y);
// Depth strategy: Base of object sorting.
// Add small offset based on type if needed, but mainly use Y
sprite.setDepth(depth);
// Apply scale if present
if (decor.scale) sprite.setScale(decor.scale);
else sprite.setScale(1);
sprite.flipX = (x + y) % 2 === 0;
// Sprites are just visual now, interaction handled by InteractionSystem via grid
// sprite.setInteractive(...) removed to fix conflicts
this.visibleDecorations.set(key, sprite);
}
}
// CROPS
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(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setTexture(`crop_stage_${crop.stage}`);
sprite.setDepth(this.iso.getDepth(x, y) + 0.5);
this.visibleCrops.set(key, sprite);
}
}
}
}
// Cleanup invisible tiles
// Cleanup tiles
for (const [key, sprite] of this.visibleTiles) {
if (!neededKeys.has(key)) {
sprite.setVisible(false);
@@ -742,7 +547,7 @@ class TerrainSystem {
}
}
// Cleanup invisible decorations
// Cleanup decorations
for (const [key, sprite] of this.visibleDecorations) {
if (!neededDecorKeys.has(key)) {
sprite.setVisible(false);
@@ -751,7 +556,7 @@ class TerrainSystem {
}
}
// Cleanup visible crops
// Cleanup crops
for (const [key, sprite] of this.visibleCrops) {
if (!neededCropKeys.has(key)) {
sprite.setVisible(false);
@@ -760,21 +565,4 @@ class TerrainSystem {
}
}
}
// Helper functions
getTerrainType(value) {
for (const type of Object.values(this.terrainTypes)) {
if (value < type.threshold) {
return type;
}
}
return this.terrainTypes.STONE;
}
getTile(gridX, gridY) {
if (gridX >= 0 && gridX < this.width && gridY >= 0 && gridY < this.height) {
return this.tiles[gridY][gridX];
}
return null;
}
}