popravek zombijo
This commit is contained in:
@@ -316,6 +316,7 @@ class NPC {
|
||||
if (this.attackCooldownTimer > 0) return;
|
||||
this.attackCooldownTimer = 1500;
|
||||
|
||||
// Attack Animation (Jump)
|
||||
if (this.sprite) {
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
@@ -324,9 +325,12 @@ class NPC {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.scene.statsSystem) {
|
||||
// Apply Damage to Player
|
||||
if (this.scene.player && this.scene.player.takeDamage) {
|
||||
console.log('🧟 Zombie ATTACKS Player!');
|
||||
this.scene.player.takeDamage(10);
|
||||
} else if (this.scene.statsSystem) {
|
||||
this.scene.statsSystem.takeDamage(10);
|
||||
this.scene.cameras.main.shake(100, 0.005);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +445,52 @@ class NPC {
|
||||
if (idx > -1) this.scene.npcs.splice(idx, 1);
|
||||
}
|
||||
|
||||
tame() {
|
||||
if (this.state === 'TAMED' || this.type !== 'zombie') return;
|
||||
|
||||
this.state = 'TAMED';
|
||||
console.log('🧟❤️ Zombie TAMED!');
|
||||
|
||||
// Visual Feedback
|
||||
const headX = this.sprite.x;
|
||||
const headY = this.sprite.y - 40;
|
||||
|
||||
const heart = this.scene.add.text(headX, headY, '❤️', { fontSize: '20px' });
|
||||
heart.setOrigin(0.5);
|
||||
heart.setDepth(this.sprite.depth + 100);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: heart,
|
||||
y: headY - 30,
|
||||
alpha: 0,
|
||||
duration: 1500,
|
||||
onComplete: () => heart.destroy()
|
||||
});
|
||||
|
||||
// Change color slightly to indicate friend
|
||||
this.sprite.setTint(0xAAFFAA);
|
||||
|
||||
// Hide Health Bar if tamed (optional)
|
||||
if (this.healthBarBg) {
|
||||
this.healthBarBg.setVisible(false);
|
||||
this.healthBar.setVisible(false);
|
||||
}
|
||||
|
||||
this.addTamedEyes();
|
||||
}
|
||||
|
||||
addTamedEyes() {
|
||||
if (this.eyesGroup) return;
|
||||
|
||||
this.eyesGroup = this.scene.add.container(this.sprite.x, this.sprite.y);
|
||||
this.eyesGroup.setDepth(this.sprite.depth + 1);
|
||||
|
||||
const eyeL = this.scene.add.rectangle(-4, -54, 4, 4, 0x00FFFF); // Cyan eyes
|
||||
const eyeR = this.scene.add.rectangle(4, -54, 4, 4, 0x00FFFF);
|
||||
|
||||
this.eyesGroup.add([eyeL, eyeR]);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.sprite) this.sprite.destroy();
|
||||
if (this.healthBar) this.healthBar.destroy();
|
||||
|
||||
@@ -21,6 +21,11 @@ class Player {
|
||||
this.direction = 'down';
|
||||
this.lastDir = { x: 0, y: 1 }; // Default south
|
||||
|
||||
// Stats
|
||||
this.hp = 100;
|
||||
this.maxHp = 100;
|
||||
this.isDead = false;
|
||||
|
||||
// Kreira sprite
|
||||
this.createSprite();
|
||||
|
||||
@@ -29,13 +34,52 @@ class Player {
|
||||
|
||||
// Space za napad
|
||||
this.scene.input.keyboard.on('keydown-SPACE', () => {
|
||||
this.attack();
|
||||
if (!this.isDead) this.attack();
|
||||
});
|
||||
|
||||
// Začetna pozicija
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
takeDamage(amount) {
|
||||
if (this.isDead) return;
|
||||
|
||||
this.hp -= amount;
|
||||
console.log(`Player HP: ${this.hp}`);
|
||||
|
||||
// Visual Feedback
|
||||
this.scene.cameras.main.shake(100, 0.01);
|
||||
this.sprite.setTint(0xff0000);
|
||||
this.scene.time.delayedCall(200, () => {
|
||||
if (!this.isDead) this.sprite.clearTint();
|
||||
});
|
||||
|
||||
// Update UI (če obstaja StatsSystem ali UI)
|
||||
if (this.scene.statsSystem) {
|
||||
// Scene specific stats update if linked
|
||||
}
|
||||
|
||||
if (this.hp <= 0) {
|
||||
this.die();
|
||||
}
|
||||
}
|
||||
|
||||
die() {
|
||||
this.isDead = true;
|
||||
this.sprite.setTint(0x555555);
|
||||
this.sprite.setRotation(Math.PI / 2); // Lie down
|
||||
console.log('💀 PLAYER DIED');
|
||||
|
||||
// Show Game Over / Reload
|
||||
const txt = this.scene.add.text(this.scene.cameras.main.midPoint.x, this.scene.cameras.main.midPoint.y, 'YOU DIED', {
|
||||
fontSize: '64px', color: '#ff0000', fontStyle: 'bold', stroke: '#000', strokeThickness: 6
|
||||
}).setOrigin(0.5).setDepth(20000);
|
||||
|
||||
this.scene.time.delayedCall(3000, () => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
createSprite() {
|
||||
// Prefer animated sprite if available
|
||||
let texKey = 'player_walk'; // Spritesheet
|
||||
|
||||
40
src/game.js
40
src/game.js
@@ -1,3 +1,43 @@
|
||||
// --- Global Error Handler ---
|
||||
class ErrorHandler {
|
||||
static init() {
|
||||
window.onerror = (message, source, lineno, colno, error) => {
|
||||
ErrorHandler.showError(message, source, lineno, colno, error);
|
||||
return false;
|
||||
};
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
ErrorHandler.showError('Unhandled Promise Rejection', '', 0, 0, event.reason);
|
||||
});
|
||||
console.log('🛡️ Global Error Handler Initialized');
|
||||
}
|
||||
|
||||
static showError(message, source, lineno, colno, error) {
|
||||
console.error('🔥 CRITICAL ERROR:', message);
|
||||
if (document.getElementById('error-overlay')) return;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.id = 'error-overlay';
|
||||
div.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(20,0,0,0.95);color:#ffaaaa;z-index:999999;display:flex;flex-direction:column;justify-content:center;align-items:center;font-family:monospace;padding:20px;text-align:center;';
|
||||
|
||||
const stack = error && error.stack ? error.stack : '';
|
||||
|
||||
div.innerHTML = `
|
||||
<h1 style="color:#ff4444;margin-bottom:20px;">☠️ OOPS! GAME CRASHED ☠️</h1>
|
||||
<div style="background:rgba(0,0,0,0.5);padding:15px;border:1px solid #ff4444;max-width:800px;max-height:300px;overflow:auto;text-align:left;margin-bottom:20px;white-space:pre-wrap;">
|
||||
<strong>${message}</strong><br>
|
||||
<small>${source}:${lineno}:${colno}</small><br><br>
|
||||
${stack}
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="window.location.reload()" style="padding:15px 30px;font-size:18px;font-weight:bold;background:#44aa44;color:white;border:none;cursor:pointer;border-radius:5px;margin-right:10px;">🔄 RELOAD GAME</button>
|
||||
<button onclick="document.getElementById('error-overlay').remove()" style="padding:15px 30px;font-size:14px;background:#666;color:white;border:none;cursor:pointer;border-radius:5px;">IGNORE</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
}
|
||||
ErrorHandler.init();
|
||||
|
||||
// Phaser Game Configuration
|
||||
const config = {
|
||||
type: Phaser.CANVAS, // Canvas renderer za pixel-perfect ostrino
|
||||
|
||||
@@ -448,8 +448,21 @@ class UIScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
update() {
|
||||
// Here we could update bars based on player stats
|
||||
// if (this.gameScene && this.gameScene.player) { ... }
|
||||
if (!this.gameScene) return;
|
||||
|
||||
// Sync HP Bar
|
||||
if (this.gameScene.player && this.healthBar) {
|
||||
const hp = this.gameScene.player.hp !== undefined ? this.gameScene.player.hp : 100;
|
||||
const maxHp = this.gameScene.player.maxHp || 100;
|
||||
this.setBarValue(this.healthBar, (hp / maxHp) * 100);
|
||||
}
|
||||
|
||||
// Sync Hunger/Thirst (if stats system exists)
|
||||
if (this.gameScene.statsSystem && this.hungerBar && this.thirstBar) {
|
||||
const stats = this.gameScene.statsSystem;
|
||||
this.setBarValue(this.hungerBar, stats.hunger);
|
||||
this.setBarValue(this.thirstBar, stats.thirst);
|
||||
}
|
||||
}
|
||||
toggleBuildMenu(isVisible) {
|
||||
if (!this.buildMenuContainer) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
107
src/utils/ErrorHandler.js
Normal file
107
src/utils/ErrorHandler.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// Global Error Handler
|
||||
// Ujame kritične napake in prikaže prijazen "Crash Screen" namesto belega zaslona.
|
||||
|
||||
export class ErrorHandler {
|
||||
static init() {
|
||||
window.onerror = (message, source, lineno, colno, error) => {
|
||||
ErrorHandler.showError(message, source, lineno, colno, error);
|
||||
// return true; // Če vrnemo true, zavremo izpis v konzolo (ne želimo tega)
|
||||
return false;
|
||||
};
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
ErrorHandler.showError('Unhandled Promise Rejection', '', 0, 0, event.reason);
|
||||
});
|
||||
|
||||
console.log('🛡️ Global Error Handler Initialized');
|
||||
}
|
||||
|
||||
static showError(message, source, lineno, colno, error) {
|
||||
console.error('🔥 CRITICAL ERROR HAUGHT:', message);
|
||||
|
||||
// Prepreči podvajanje overlayev
|
||||
if (document.getElementById('error-overlay')) return;
|
||||
|
||||
// Ustvari overlay element
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'error-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background-color: rgba(20, 0, 0, 0.95);
|
||||
color: #ffaaaa;
|
||||
z-index: 999999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const title = document.createElement('h1');
|
||||
title.innerText = '💀 OOPS! GAME CRASHED 💀';
|
||||
title.style.color = '#ff4444';
|
||||
title.style.marginBottom = '20px';
|
||||
|
||||
const msgBox = document.createElement('div');
|
||||
msgBox.innerText = `${message}\n\nFile: ${source}\nLine: ${lineno}:${colno}\n\n${error ? error.stack : ''}`;
|
||||
msgBox.style.cssText = `
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 15px;
|
||||
border: 1px solid #ff4444;
|
||||
max-width: 800px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
margin-bottom: 30px;
|
||||
color: #fff;
|
||||
`;
|
||||
|
||||
const btnContainer = document.createElement('div');
|
||||
|
||||
const btnReload = document.createElement('button');
|
||||
btnReload.innerText = '🔄 RELOAD GAME';
|
||||
btnReload.style.cssText = `
|
||||
padding: 15px 30px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background: #44aa44;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
margin-right: 15px;
|
||||
`;
|
||||
btnReload.onclick = () => window.location.reload();
|
||||
|
||||
const btnIgnore = document.createElement('button');
|
||||
btnIgnore.innerText = 'IGNORE & TRY TO CONTINUE';
|
||||
btnIgnore.style.cssText = `
|
||||
padding: 15px 30px;
|
||||
font-size: 14px;
|
||||
background: #666;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
`;
|
||||
btnIgnore.onclick = () => {
|
||||
overlay.remove();
|
||||
console.log('⚠️ User ignored critical error.');
|
||||
};
|
||||
|
||||
btnContainer.appendChild(btnReload);
|
||||
btnContainer.appendChild(btnIgnore);
|
||||
|
||||
overlay.appendChild(title);
|
||||
overlay.appendChild(msgBox);
|
||||
overlay.appendChild(btnContainer);
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
}
|
||||
@@ -571,6 +571,49 @@ class TextureGenerator {
|
||||
ctx.lineTo(32, 40);
|
||||
ctx.fill();
|
||||
|
||||
} else if (type === 'market') {
|
||||
// Market Stall
|
||||
// Wooden base
|
||||
ctx.fillStyle = '#8B4513';
|
||||
ctx.fillRect(4, 30, 56, 34); // Big base
|
||||
|
||||
// Counter
|
||||
ctx.fillStyle = '#DEB887'; // Burlywood
|
||||
ctx.fillRect(0, 40, 64, 10);
|
||||
|
||||
// Posts
|
||||
ctx.fillStyle = '#5C4033';
|
||||
ctx.fillRect(4, 10, 4, 30);
|
||||
ctx.fillRect(56, 10, 4, 30);
|
||||
|
||||
// Striped Roof
|
||||
ctx.fillStyle = '#FF0000'; // Red
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 0);
|
||||
ctx.lineTo(64, 15);
|
||||
ctx.lineTo(0, 15);
|
||||
ctx.fill();
|
||||
|
||||
// White stripes
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 0);
|
||||
ctx.lineTo(40, 15);
|
||||
ctx.lineTo(32, 15);
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 0);
|
||||
ctx.lineTo(24, 15);
|
||||
ctx.lineTo(16, 15);
|
||||
ctx.fill();
|
||||
|
||||
// Goods (Apples/Items)
|
||||
ctx.fillStyle = '#00FF00';
|
||||
ctx.beginPath(); ctx.arc(10, 38, 4, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.fillStyle = '#FF0000';
|
||||
ctx.beginPath(); ctx.arc(20, 38, 4, 0, Math.PI * 2); ctx.fill();
|
||||
|
||||
} else if (type === 'ruin') {
|
||||
// Isometric Ruin
|
||||
|
||||
@@ -922,5 +965,29 @@ class TextureGenerator {
|
||||
|
||||
canvas.refresh();
|
||||
}
|
||||
|
||||
// 7. BONE
|
||||
if (!scene.textures.exists('item_bone')) {
|
||||
const size = 32;
|
||||
const canvas = scene.textures.createCanvas('item_bone', size, size);
|
||||
const ctx = canvas.getContext();
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
|
||||
// Bone shape
|
||||
ctx.strokeStyle = '#E8E8E8'; // Off-white
|
||||
ctx.lineWidth = 6;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(10, 10);
|
||||
ctx.lineTo(22, 22);
|
||||
ctx.stroke();
|
||||
|
||||
// Knobs
|
||||
ctx.fillStyle = '#E8E8E8';
|
||||
ctx.beginPath(); ctx.arc(10, 10, 4, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(22, 22, 4, 0, Math.PI * 2); ctx.fill();
|
||||
|
||||
canvas.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user