This commit is contained in:
2025-12-07 21:31:44 +01:00
parent 4a0ca267ea
commit 974141c08c
52 changed files with 2485 additions and 397 deletions

44
src/utils/Compression.js Normal file
View File

@@ -0,0 +1,44 @@
// Utility class for string compression (LZW algorithm)
// Used to reduce save file size in localStorage
class Compression {
static compress(s) {
if (s == null) return "";
var dict = {}, data = (s + "").split(""), out = [], currChar, phrase = data[0], code = 256;
for (var i = 1; i < data.length; i++) {
currChar = data[i];
if (dict[phrase + currChar] != null) {
phrase += currChar;
} else {
out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
dict[phrase + currChar] = code;
code++;
phrase = currChar;
}
}
out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
for (var i = 0; i < out.length; i++) {
out[i] = String.fromCharCode(out[i]);
}
return out.join("");
}
static decompress(s) {
if (s == null) return "";
if (s == "") return null;
var dict = {}, data = (s + "").split(""), currChar = data[0], oldPhrase = currChar, out = [currChar], code = 256, phrase;
for (var i = 1; i < data.length; i++) {
var currCode = data[i].charCodeAt(0);
if (currCode < 256) {
phrase = data[i];
} else {
phrase = dict[currCode] ? dict[currCode] : (oldPhrase + oldPhrase.charAt(0));
}
out.push(phrase);
currChar = phrase.charAt(0);
dict[code] = oldPhrase + currChar;
code++;
oldPhrase = phrase;
}
return out.join("");
}
}

99
src/utils/Pathfinding.js Normal file
View File

@@ -0,0 +1,99 @@
class Pathfinding {
constructor(scene) {
this.scene = scene;
// Offsets for 4 directions (Isometric grid)
this.dirs = [
{ x: 0, y: -1 }, // Up
{ x: 0, y: 1 }, // Down
{ x: -1, y: 0 }, // Left
{ x: 1, y: 0 } // Right
];
}
findPath(startX, startY, endX, endY, limit = 50) {
// Simple A* implementation
const startNode = { x: startX, y: startY, g: 0, h: 0, f: 0, parent: null };
const openList = [startNode];
const closedSet = new Set();
let count = 0;
while (openList.length > 0 && count < limit) {
count++;
// Sort by F cost (can be optimized with PriorityQueue)
openList.sort((a, b) => a.f - b.f);
const currentNode = openList.shift();
// Reached destination? (or close enough)
if (currentNode.x === endX && currentNode.y === endY) {
return this.reconstructPath(currentNode);
}
const key = `${currentNode.x},${currentNode.y}`;
closedSet.add(key);
// Explore neighbors
for (const dir of this.dirs) {
const neighborX = currentNode.x + dir.x;
const neighborY = currentNode.y + dir.y;
if (closedSet.has(`${neighborX},${neighborY}`)) continue;
// Check collision
if (!this.isValidMove(neighborX, neighborY)) continue;
const gScore = currentNode.g + 1;
const hScore = Math.abs(neighborX - endX) + Math.abs(neighborY - endY);
const fScore = gScore + hScore;
// Check if already in openList with lower G
const existing = openList.find(n => n.x === neighborX && n.y === neighborY);
if (existing) {
if (gScore < existing.g) {
existing.g = gScore;
existing.f = fScore;
existing.parent = currentNode;
}
} else {
openList.push({ x: neighborX, y: neighborY, g: gScore, h: hScore, f: fScore, parent: currentNode });
}
}
}
// Limit reached or no path - return simplified direction if possible
return null;
}
reconstructPath(node) {
const path = [];
let curr = node;
while (curr.parent) {
path.unshift({ x: curr.x, y: curr.y });
curr = curr.parent;
}
return path;
}
isValidMove(x, y) {
const terrain = this.scene.terrainSystem;
if (!terrain) return true;
// Bounds
if (x < 0 || y < 0 || x >= terrain.width || y >= terrain.height) return false;
// Tile Type
const tile = terrain.tiles[y][x];
if (tile.type.name === 'water') return false;
// Decorations collision
const key = `${x},${y}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
const solidTypes = ['tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone'];
if (solidTypes.includes(decor.type)) return false;
}
return true;
}
}

View File

@@ -268,18 +268,43 @@ class TextureGenerator {
x.fillStyle = 'gray'; x.fillRect(8, 12, 16, 4);
c.refresh();
}
if (!scene.textures.exists('item_hoe')) {
const c = scene.textures.createCanvas('item_hoe', 32, 32);
const x = c.getContext();
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 18); // Ročaj
x.fillStyle = 'gray';
x.fillRect(10, 8, 12, 4); // Rezilo motike
c.refresh();
}
if (!scene.textures.exists('item_sword')) {
const c = scene.textures.createCanvas('item_sword', 32, 32);
const x = c.getContext();
x.fillStyle = 'brown'; x.fillRect(14, 10, 4, 14); // Ročaj
x.fillStyle = 'gray';
x.fillRect(12, 8, 8, 12); // Rezilo meča
x.fillStyle = 'gold'; x.fillRect(14, 21, 4, 2); // Guard
c.refresh();
}
}
static createItemSprites(scene) {
// Placeholder item generation
const items = ['wood', 'stone', 'seed', 'item_bone']; // Ensure item_bone is here
items.forEach(it => {
const items = [
{ name: 'wood', color: '#8B4513' }, // Rjava
{ name: 'stone', color: '#808080' }, // Siva
{ name: 'seeds', color: '#90EE90' }, // Svetlo zelena
{ name: 'wheat', color: '#FFD700' }, // Zlata
{ name: 'item_bone', color: '#F5F5DC' } // Beige
];
items.forEach(item => {
const it = typeof item === 'string' ? item : item.name;
const color = typeof item === 'string' ? 'gold' : item.color;
const k = (it.startsWith('item_')) ? it : 'item_' + it;
if (!scene.textures.exists(k)) {
const c = scene.textures.createCanvas(k, 32, 32);
const x = c.getContext();
x.clearRect(0, 0, 32, 32);
x.fillStyle = 'gold';
x.fillStyle = color;
x.beginPath(); x.arc(16, 16, 10, 0, Math.PI * 2); x.fill();
c.refresh();
}