udomacenje zombija in uboj\

This commit is contained in:
2025-12-07 12:47:47 +01:00
parent 8e401a9d6f
commit 2404d44ef7
10 changed files with 1086 additions and 532 deletions

View File

@@ -8,7 +8,7 @@ Cilj: Izboljšati delovanje igre na počasnejših računalnikih (low-end devices
Trenutno `TerrainSystem` uporablja individualne `Phaser.GameObjects.Sprite` za vsako ploščico (tile) na tleh. Pri mapi 100x100 to pomeni 10.000 spritov, kar je veliko breme za CPU/GPU, tudi s cullingom. Trenutno `TerrainSystem` uporablja individualne `Phaser.GameObjects.Sprite` za vsako ploščico (tile) na tleh. Pri mapi 100x100 to pomeni 10.000 spritov, kar je veliko breme za CPU/GPU, tudi s cullingom.
* **Rešitev:** Uporabiti `Phaser.Tilemaps` za izometrični pogled ali `Phaser.GameObjects.Blitter`. Blitter je izjemno hiter za renderiranje velikega števila enakih tekstur. * **Rešitev:** Uporabiti `Phaser.Tilemaps` za izometrični pogled ali `Phaser.GameObjects.Blitter`. Blitter je izjemno hiter za renderiranje velikega števila enakih tekstur.
* **Pričakovan prihranek:** Ogromen (CPU overhead za game objekte). * **Pričakovan prihranek:** Ogromen (CPU overhead za game objekte).
s
### B. Optimizacija Depth Sorting (Srednja prioriteta) ### B. Optimizacija Depth Sorting (Srednja prioriteta)
Trenutno se globina (z-index) računa in nastavlja za vsak `Sprite` (igralec, NPC, tiles, decorations) pogosto vsak frame. Trenutno se globina (z-index) računa in nastavlja za vsak `Sprite` (igralec, NPC, tiles, decorations) pogosto vsak frame.
* **Rešitev:** * **Rešitev:**
@@ -64,7 +64,8 @@ Dodati meni "Settings", kjer lahko uporabnik izbere:
## Akcijski Plan (Koraki) ## Akcijski Plan (Koraki)
1. **Korak 1 (Takoj):** Implementiraj "Chunk System" za `updateCulling`. Ne iteriraj čez 10.000 tileov vsak frame. Iteriraj samo čez vidno območje intersekcije (minX do maxX, minY do maxY). To je enostaven matematičen fix v `TerrainSystem`. 1. **Korak 1 (Takoj):** [DONE] Implementiraj "Throttling" za `updateCulling`.
2. **Korak 2:** Prepiši `WeatherSystem` za uporabo preprostih delcev ali zmanjšaj število kapelj na low-end. 2. **Korak 2:** [DONE] Prepiši `WeatherSystem` za uporabo Phaser Particles.
3. **Korak 3:** Implementiraj "Throttling" za NPC AI. 3. **Korak 3:** [DONE] Implementiraj "Throttling" in Distance Culling za NPC AI.
4. **Korak 4:** Prehod na `Blitter` za teren (dolgoročno). 4. **Korak 4:** [DONE] Implementacija `Phaser.Blitter` za teren (Faza 4 fix).
5. **Korak 5:** [DONE] Povezava Settings z Weather in Terrain sistemi.

View File

@@ -14,14 +14,17 @@ class NPC {
this.iso = new IsometricUtils(48, 24); this.iso = new IsometricUtils(48, 24);
// Random walk paramters // Random walk paramters
this.moveSpeed = 100; // px/s (počasnejše od igralca) this.moveSpeed = 100; // px/s
this.gridMoveTime = 300; // ms za premik (počasneje) this.gridMoveTime = 300; // ms
// Stanje // Stanje
this.state = 'WANDER'; // WANDER, TAMED, FOLLOW this.state = 'WANDER'; // WANDER, TAMED, FOLLOW
this.isMoving = false; this.isMoving = false;
this.pauseTime = 0; this.pauseTime = 0;
this.maxPauseTime = 2000; // Pavza med premiki (2s) this.maxPauseTime = 2000;
this.attackCooldownTimer = 0;
this.hp = 20;
this.maxHp = 20;
// Kreira sprite // Kreira sprite
this.createSprite(); this.createSprite();
@@ -57,15 +60,47 @@ class NPC {
// Change color slightly to indicate friend // Change color slightly to indicate friend
this.sprite.setTint(0xAAFFAA); this.sprite.setTint(0xAAFFAA);
// Hide Health Bar if tamed
if (this.healthBarBg) {
this.healthBarBg.setVisible(false);
this.healthBar.setVisible(false);
}
// Change Eyes to Friendly (Cyan)
this.addTamedEyes();
}
addTamedEyes() {
if (this.eyesGroup) return;
// Container for eyes
// Coordinates relative to sprite center bottom (0.5, 1)
// Head is roughly at y - height.
// Assuming sprite height ~50-60px visual.
this.eyesGroup = this.scene.add.container(this.sprite.x, this.sprite.y);
// Eyes
const eyeL = this.scene.add.rectangle(-5, -55, 3, 3, 0x00FFFF);
const eyeR = this.scene.add.rectangle(5, -55, 3, 3, 0x00FFFF);
this.eyesGroup.add([eyeL, eyeR]);
this.eyesGroup.setDepth(this.sprite.depth + 2);
} }
toggleState() { toggleState() {
if (this.state === 'WANDER') { if (this.state === 'WANDER') {
this.tame(); this.tame();
} else { } else {
// Maybe command to stay/follow? // Command to stay/follow
this.state = this.state === 'FOLLOW' ? 'STAY' : 'FOLLOW'; this.state = this.state === 'FOLLOW' ? 'STAY' : 'FOLLOW';
console.log(`Command: ${this.state}`); console.log(`Command: ${this.state}`);
// Visual feedback for command
const txt = this.scene.add.text(this.sprite.x, this.sprite.y - 60, this.state, { fontSize: '12px', color: '#FFF' });
txt.setOrigin(0.5);
this.scene.tweens.add({ targets: txt, y: txt.y - 20, alpha: 0, duration: 1000, onComplete: () => txt.destroy() });
} }
} }
@@ -100,12 +135,21 @@ class NPC {
this.sprite.setScale(0.3); this.sprite.setScale(0.3);
} }
// HEALTH BAR
this.healthBarBg = this.scene.add.graphics();
this.healthBarBg.fillStyle(0x000000, 0.5);
this.healthBarBg.fillRect(-16, -70, 32, 4);
this.healthBarBg.setVisible(false);
this.healthBar = this.scene.add.graphics();
this.healthBar.fillStyle(0x00ff00, 1);
this.healthBar.fillRect(-16, -70, 32, 4);
this.healthBar.setVisible(false);
// Depth sorting // Depth sorting
this.updateDepth(); this.updateDepth();
} }
// ... loop update ...
moveToGrid(targetX, targetY) { moveToGrid(targetX, targetY) {
// Determine facing direction before moving // Determine facing direction before moving
const dx = targetX - this.gridX; const dx = targetX - this.gridX;
@@ -133,6 +177,7 @@ class NPC {
ease: 'Linear', ease: 'Linear',
onComplete: () => { onComplete: () => {
this.isMoving = false; this.isMoving = false;
this.updatePosition();
// Stop Animation // Stop Animation
if (this.sprite.texture.key === 'zombie_walk') { if (this.sprite.texture.key === 'zombie_walk') {
@@ -147,106 +192,259 @@ class NPC {
} }
update(delta) { update(delta) {
this.updateDepth(); // Continuous depth sorting // 1. Distance Culling
if (this.scene.player) {
const playerPos = this.scene.player.getPosition();
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
const cullDist = (this.state === 'CHASE' || this.state === 'FOLLOW') ? 50 : 30;
if (this.isMoving) { if (dist > cullDist) {
return; // Že se premika if (this.sprite.visible) {
this.sprite.setVisible(false);
if (this.healthBar) {
this.healthBar.setVisible(false);
this.healthBarBg.setVisible(false);
}
if (this.eyesGroup) this.eyesGroup.setVisible(false);
}
return;
} else {
if (!this.sprite.visible) {
this.sprite.setVisible(true);
if (this.eyesGroup) this.eyesGroup.setVisible(true);
// Bars stay hidden until damaged usually
}
}
} }
// Tamed logic // 2. Optimization: Update depth ONLY if moving
if (this.isMoving) {
this.updateDepth();
this.updatePosition();
return;
}
// 3. AI Logic
if (this.type === 'zombie' && this.state !== 'TAMED' && this.state !== 'FOLLOW') {
this.handleAggressiveAI(delta);
} else {
this.handlePassiveAI(delta);
}
this.updatePosition();
}
handlePassiveAI(delta) {
if (this.state === 'TAMED' || this.state === 'FOLLOW') { if (this.state === 'TAMED' || this.state === 'FOLLOW') {
this.followPlayer(); this.followPlayer();
return; return;
} }
// Random walk - pavza med premiki
this.pauseTime += delta; this.pauseTime += delta;
if (this.pauseTime >= this.maxPauseTime) { if (this.pauseTime >= this.maxPauseTime) {
this.performRandomWalk(); this.performRandomWalk();
this.pauseTime = 0; this.pauseTime = 0;
} }
} }
followPlayer() { handleAggressiveAI(delta) {
if (!this.scene.player) return; if (!this.scene.player) return;
const playerPos = this.scene.player.getPosition(); const playerPos = this.scene.player.getPosition();
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y); const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
// Če je precej dlje, se premakni k njemu if (this.attackCooldownTimer > 0) {
if (dist > 2) { this.attackCooldownTimer -= delta;
const dx = Math.sign(playerPos.x - this.gridX); }
const dy = Math.sign(playerPos.y - this.gridY);
// Move 1 tile towards player if (this.state === 'WANDER') {
let targetX = this.gridX + dx; if (dist < 8) {
let targetY = this.gridY + dy; this.state = 'CHASE';
this.showEmote('!');
// Avoid occupying SAME tile as player } else {
if (targetX === playerPos.x && targetY === playerPos.y) { this.pauseTime += delta;
if (Math.random() < 0.5) targetX = this.gridX; if (this.pauseTime >= this.maxPauseTime) {
else targetY = this.gridY; this.performRandomWalk();
this.pauseTime = 0;
}
} }
}
else if (this.state === 'CHASE') {
if (dist > 15) {
this.state = 'WANDER';
return;
}
if (dist <= 1.5) {
this.tryAttack();
} else {
this.moveTowards(playerPos.x, playerPos.y);
}
}
}
this.moveToGrid(targetX, targetY); moveTowards(targetX, targetY) {
const dx = Math.sign(targetX - this.gridX);
const dy = Math.sign(targetY - this.gridY);
let nextX = this.gridX + dx;
let nextY = this.gridY + dy;
if (this.isValidMove(nextX, nextY)) {
this.moveToGrid(nextX, nextY);
} else if (this.isValidMove(this.gridX + dx, this.gridY)) {
this.moveToGrid(this.gridX + dx, this.gridY);
} else if (this.isValidMove(this.gridX, this.gridY + dy)) {
this.moveToGrid(this.gridX, this.gridY + dy);
}
}
isValidMove(x, y) {
const terrainSystem = this.scene.terrainSystem;
if (!terrainSystem) return true;
if (!this.iso.isInBounds(x, y, terrainSystem.width, terrainSystem.height)) return false;
if (terrainSystem.tiles[y][x].type.name === 'water') return false;
const key = `${x},${y}`;
if (terrainSystem.decorationsMap.has(key)) {
const decor = terrainSystem.decorationsMap.get(key);
const solidTypes = ['tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone'];
if (solidTypes.includes(decor.type)) return false;
}
return true;
}
tryAttack() {
if (this.attackCooldownTimer > 0) return;
this.attackCooldownTimer = 1500;
if (this.sprite) {
this.scene.tweens.add({
targets: this.sprite,
y: this.sprite.y - 10,
yoyo: true, duration: 100, repeat: 1
});
}
if (this.scene.statsSystem) {
this.scene.statsSystem.takeDamage(10);
this.scene.cameras.main.shake(100, 0.005);
} }
} }
performRandomWalk() { performRandomWalk() {
// Naključna smer (NSEW + možnost obstati) const dirs = [{ x: 0, y: -1 }, { x: 0, y: 1 }, { x: -1, y: 0 }, { x: 1, y: 0 }];
const directions = [ const dir = dirs[Math.floor(Math.random() * dirs.length)];
{ x: -1, y: 0 }, // North-West const nextX = this.gridX + dir.x;
{ x: 1, y: 0 }, // South-East const nextY = this.gridY + dir.y;
{ x: 0, y: -1 }, // South-West if (this.isValidMove(nextX, nextY)) {
{ x: 0, y: 1 }, // North-East this.moveToGrid(nextX, nextY);
{ x: 0, y: 0 } // Stay (30% možnost)
];
const dir = Phaser.Math.RND.pick(directions);
const targetX = this.gridX + dir.x;
const targetY = this.gridY + dir.y;
// Preveri kolizijo z robovi
const terrainSystem = this.scene.terrainSystem;
if (terrainSystem && this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) {
// Preveri da ni ista pozicija kot igralec
if (this.scene.player) {
const playerPos = this.scene.player.getPosition();
if (targetX === playerPos.x && targetY === playerPos.y) {
return; // Ne premakni se na igralca
}
}
if (dir.x !== 0 || dir.y !== 0) {
this.moveToGrid(targetX, targetY);
}
} }
} }
followPlayer() {
if (!this.scene.player) return;
const playerPos = this.scene.player.getPosition();
const dist = Phaser.Math.Distance.Between(this.gridX, this.gridY, playerPos.x, playerPos.y);
if (dist < 2) return;
this.moveTowards(playerPos.x, playerPos.y);
}
showEmote(text) {
const emote = this.scene.add.text(this.sprite.x, this.sprite.y - 40, text, { fontSize: '20px', fontStyle: 'bold', color: '#FF0000' });
emote.setOrigin(0.5);
emote.setDepth(this.sprite.depth + 100);
this.scene.tweens.add({ targets: emote, y: emote.y - 20, alpha: 0, duration: 1000, onComplete: () => emote.destroy() });
}
updatePosition() { updatePosition() {
if (!this.sprite) return;
const screenPos = this.iso.toScreen(this.gridX, this.gridY); const screenPos = this.iso.toScreen(this.gridX, this.gridY);
this.sprite.setPosition(
screenPos.x + this.offsetX, if (!this.isMoving) {
screenPos.y + this.offsetY this.sprite.setPosition(
); screenPos.x + this.offsetX,
screenPos.y + this.offsetY
);
}
// BARS
if (this.healthBar && this.healthBarBg) {
this.healthBarBg.x = this.sprite.x;
this.healthBarBg.y = this.sprite.y;
this.healthBar.x = this.sprite.x;
this.healthBar.y = this.sprite.y;
this.healthBarBg.setDepth(this.sprite.depth + 100);
this.healthBar.setDepth(this.sprite.depth + 101);
}
// EYES
if (this.eyesGroup) {
this.eyesGroup.setPosition(this.sprite.x, this.sprite.y);
this.eyesGroup.setDepth(this.sprite.depth + 2);
}
this.updateDepth(); this.updateDepth();
} }
updateDepth() { updateDepth() {
// Pixel perfect sorting if (this.sprite) {
if (this.sprite) this.sprite.setDepth(this.sprite.y); this.sprite.setDepth(this.sprite.y);
}
} }
getPosition() { takeDamage(amount) {
return { x: this.gridX, y: this.gridY }; this.hp -= amount;
// Show Health Bar
if (this.healthBar) {
this.healthBar.setVisible(true);
this.healthBarBg.setVisible(true);
// Update width
const percent = Math.max(0, this.hp / this.maxHp);
this.healthBar.clear();
this.healthBar.fillStyle(percent < 0.3 ? 0xff0000 : 0x00ff00, 1);
this.healthBar.fillRect(-16, -70, 32 * percent, 4);
}
if (this.sprite) {
this.sprite.setTint(0xff0000);
this.scene.time.delayedCall(100, () => {
if (this.sprite) {
this.sprite.clearTint();
// Re-apply tamed tint if tamed
if (this.state === 'TAMED' || this.state === 'FOLLOW' || this.state === 'STAY') {
this.sprite.setTint(0xAAFFAA);
}
}
});
}
console.log(`Zombie HP: ${this.hp}`);
if (this.hp <= 0) {
this.die();
}
}
die() {
console.log('🧟💀 Zombie DEAD');
// Spawn loot - BONE
if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) {
this.scene.interactionSystem.spawnLoot(this.gridX, this.gridY, 'item_bone');
}
this.destroy();
const idx = this.scene.npcs.indexOf(this);
if (idx > -1) this.scene.npcs.splice(idx, 1);
} }
destroy() { destroy() {
if (this.sprite) { if (this.sprite) this.sprite.destroy();
this.sprite.destroy(); if (this.healthBar) this.healthBar.destroy();
} if (this.healthBarBg) this.healthBarBg.destroy();
if (this.eyesGroup) this.eyesGroup.destroy();
} }
} }

View File

@@ -6,19 +6,20 @@ class Player {
this.gridX = gridX; this.gridX = gridX;
this.gridY = gridY; this.gridY = gridY;
// Terrain offset (za sinhronizacijo s terrain containerjem) // Terrain offset
this.offsetX = offsetX; this.offsetX = offsetX;
this.offsetY = offsetY; this.offsetY = offsetY;
this.iso = new IsometricUtils(48, 24); this.iso = new IsometricUtils(48, 24);
// Hitrostgibanja // Hitrost gibanja
this.moveSpeed = 150; // px/s this.moveSpeed = 150; // px/s
this.gridMoveTime = 200; // ms za premik na eno kocko this.gridMoveTime = 200; // ms za premik na eno kocko
// Stanje // Stanje
this.isMoving = false; this.isMoving = false;
this.direction = 'down'; this.direction = 'down';
this.lastDir = { x: 0, y: 1 }; // Default south
// Kreira sprite // Kreira sprite
this.createSprite(); this.createSprite();
@@ -26,6 +27,11 @@ class Player {
// Setup kontrole // Setup kontrole
this.setupControls(); this.setupControls();
// Space za napad
this.scene.input.keyboard.on('keydown-SPACE', () => {
this.attack();
});
// Začetna pozicija // Začetna pozicija
this.updatePosition(); this.updatePosition();
} }
@@ -53,7 +59,7 @@ class Player {
// Scale logic // Scale logic
if (isAnimated) { if (isAnimated) {
this.sprite.setScale(1.5); // 64px frame -> looks good around 96px total height relative to 48px tile this.sprite.setScale(1.5);
} else { } else {
this.sprite.setScale(0.3); this.sprite.setScale(0.3);
} }
@@ -61,58 +67,17 @@ class Player {
// --- HAND / HELD ITEM SPRITE --- // --- HAND / HELD ITEM SPRITE ---
this.handSprite = this.scene.add.sprite( this.handSprite = this.scene.add.sprite(
screenPos.x + this.offsetX + 10, screenPos.x + this.offsetX + 10,
screenPos.y + this.offsetY - 25, // Adjusted for new height screenPos.y + this.offsetY - 25,
'item_axe' 'item_axe'
); );
this.handSprite.setOrigin(0.5, 0.5); this.handSprite.setOrigin(0.5, 0.5);
this.handSprite.setScale(0.25); this.handSprite.setScale(0.25);
this.handSprite.setVisible(false); this.handSprite.setVisible(false);
// Depth sorting
this.updateDepth();
}
// ... setupControls ...
// ... update ...
moveToGrid(targetX, targetY) {
this.isMoving = true;
this.gridX = targetX;
this.gridY = targetY;
const targetScreen = this.iso.toScreen(targetX, targetY);
// Play Animation
if (this.sprite.texture.key === 'player_walk') {
this.sprite.play('player_walk_anim', true);
}
// Tween za smooth gibanje
this.scene.tweens.add({
targets: [this.sprite, this.handSprite], // Move both
x: '+=' + (targetScreen.x + this.offsetX - this.sprite.x),
y: '+=' + (targetScreen.y + this.offsetY - this.sprite.y),
duration: this.gridMoveTime,
ease: 'Linear',
onComplete: () => {
this.isMoving = false;
this.updatePosition();
// Stop Animation
if (this.sprite.texture.key === 'player_walk') {
this.sprite.stop();
this.sprite.setFrame(0); // Idle frame
}
}
});
// Posodobi depth
this.updateDepth(); this.updateDepth();
} }
setupControls() { setupControls() {
// WASD kontrole
this.keys = this.scene.input.keyboard.addKeys({ this.keys = this.scene.input.keyboard.addKeys({
up: Phaser.Input.Keyboard.KeyCodes.W, up: Phaser.Input.Keyboard.KeyCodes.W,
down: Phaser.Input.Keyboard.KeyCodes.S, down: Phaser.Input.Keyboard.KeyCodes.S,
@@ -121,14 +86,43 @@ class Player {
}); });
} }
attack() {
console.log('⚔️ Player Attack!');
if (this.scene.interactionSystem) {
const targetX = this.gridX + this.lastDir.x;
const targetY = this.gridY + this.lastDir.y;
this.scene.interactionSystem.handleInteraction(targetX, targetY, true); // true = attackMode
}
// Animation
this.scene.tweens.add({
targets: this.handSprite,
angle: 45, // Swing
yoyo: true,
duration: 100
});
// Player lunge
const lungeX = this.sprite.x + (this.lastDir.x * 10);
const lungeY = this.sprite.y + (this.lastDir.y * 5);
this.scene.tweens.add({
targets: this.sprite,
x: lungeX,
y: lungeY,
yoyo: true,
duration: 50
});
}
update(delta) { update(delta) {
this.updateDepth(); if (this.isMoving) {
this.updateDepth();
}
if (!this.isMoving) { if (!this.isMoving) {
this.handleInput(); this.handleInput();
} }
// Sync Held Item with Inventory
this.updateHeldItem(); this.updateHeldItem();
} }
@@ -158,47 +152,110 @@ class Player {
let targetX = this.gridX; let targetX = this.gridX;
let targetY = this.gridY; let targetY = this.gridY;
let moved = false; let moved = false;
let facingRight = !this.sprite.flipX; // Keep current let facingRight = !this.sprite.flipX;
// WASD za isometric movement // WASD
if (this.keys.up.isDown) { // North-West let dx = 0;
targetX--; let dy = 0;
if (this.keys.up.isDown) {
dx = -1; dy = 0;
moved = true; moved = true;
facingRight = false; // Left-ish facingRight = false;
} else if (this.keys.down.isDown) { // South-East } else if (this.keys.down.isDown) {
targetX++; dx = 1; dy = 0;
moved = true; moved = true;
facingRight = true; // Right-ish facingRight = true;
} }
if (this.keys.left.isDown) { // South-West if (this.keys.left.isDown) {
targetY++; // SWAPPED: Was -- dx = 0; dy = 1;
moved = true; moved = true;
facingRight = false; // Left-ish facingRight = false;
} else if (this.keys.right.isDown) { // North-East } else if (this.keys.right.isDown) {
targetY--; // SWAPPED: Was ++ dx = 0; dy = -1;
moved = true; moved = true;
facingRight = true; // Right-ish facingRight = true;
} }
// Apply Facing // Update target
this.sprite.setFlipX(!facingRight); targetX = this.gridX + dx;
targetY = this.gridY + dy;
// Update Hand Position based on facing // Update Facing Direction and Last Dir
const handOffset = facingRight ? 10 : -10; if (moved) {
this.handSprite.setX(this.sprite.x + handOffset); // Keep diagonal input clean or prioritize one axis?
this.handSprite.setFlipX(!facingRight); // Just use the calculated dx/dy.
// Note: If both UP and LEFT pressed, logic above overwrites dx/dy.
// Let's refine to allow diagonal accumulation if needed, but existing logic prioritized axis.
// Current logic: RIGHT/LEFT overwrites UP/DOWN. This is fine for now.
// Preveri kolizijo z robovi mape this.lastDir = { x: dx, y: dy };
this.sprite.setFlipX(!facingRight);
// Hand offset
const handOffset = facingRight ? 10 : -10;
this.handSprite.setX(this.sprite.x + handOffset);
this.handSprite.setFlipX(!facingRight);
}
// Collision Check
const terrainSystem = this.scene.terrainSystem; const terrainSystem = this.scene.terrainSystem;
if (moved && terrainSystem) { if (moved && terrainSystem) {
if (this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) { if (this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) {
this.moveToGrid(targetX, targetY);
const tile = terrainSystem.tiles[targetY][targetX];
let isPassable = true;
if (tile.type.name === 'water') isPassable = false;
const key = `${targetX},${targetY}`;
if (terrainSystem.decorationsMap.has(key)) {
const decor = terrainSystem.decorationsMap.get(key);
const solidTypes = ['tree', 'stone', 'bush', 'wall', 'ruin', 'fence', 'house', 'gravestone'];
if (solidTypes.includes(decor.type)) {
console.log('⛔ Blocked by:', decor.type);
isPassable = false;
}
}
if (isPassable) {
this.moveToGrid(targetX, targetY);
}
} }
} }
} }
moveToGrid(targetX, targetY) {
this.isMoving = true;
this.gridX = targetX;
this.gridY = targetY;
const targetScreen = this.iso.toScreen(targetX, targetY);
if (this.sprite.texture.key === 'player_walk') {
this.sprite.play('player_walk_anim', true);
}
this.scene.tweens.add({
targets: [this.sprite, this.handSprite],
x: '+=' + (targetScreen.x + this.offsetX - this.sprite.x),
y: '+=' + (targetScreen.y + this.offsetY - this.sprite.y),
duration: this.gridMoveTime,
ease: 'Linear',
onComplete: () => {
this.isMoving = false;
this.updatePosition();
if (this.sprite.texture.key === 'player_walk') {
this.sprite.stop();
this.sprite.setFrame(0);
}
}
});
this.updateDepth();
}
updatePosition() { updatePosition() {
const screenPos = this.iso.toScreen(this.gridX, this.gridY); const screenPos = this.iso.toScreen(this.gridX, this.gridY);
@@ -207,7 +264,6 @@ class Player {
screenPos.y + this.offsetY screenPos.y + this.offsetY
); );
// Update hand
const facingRight = !this.sprite.flipX; const facingRight = !this.sprite.flipX;
const handOffset = facingRight ? 10 : -10; const handOffset = facingRight ? 10 : -10;
this.handSprite.setPosition( this.handSprite.setPosition(
@@ -219,7 +275,6 @@ class Player {
} }
updateDepth() { updateDepth() {
// Pixel-perfect depth sorting based on screen Y
if (this.sprite) { if (this.sprite) {
this.sprite.setDepth(this.sprite.y); this.sprite.setDepth(this.sprite.y);
if (this.handSprite) this.handSprite.setDepth(this.sprite.y + 1); if (this.handSprite) this.handSprite.setDepth(this.sprite.y + 1);
@@ -235,9 +290,7 @@ class Player {
} }
destroy() { destroy() {
if (this.sprite) { if (this.sprite) this.sprite.destroy();
this.sprite.destroy();
}
} }
dieAnimation() { dieAnimation() {

View File

@@ -6,6 +6,13 @@ class GameScene extends Phaser.Scene {
this.terrainContainer = null; this.terrainContainer = null;
this.player = null; this.player = null;
this.npcs = []; // Array za NPCje this.npcs = []; // Array za NPCje
// Settings
this.settings = {
viewDistance: 'HIGH', // LOW, MEDIUM, HIGH
particles: 'HIGH', // NONE, LOW, HIGH
shadows: true
};
} }
create() { create() {
@@ -108,6 +115,9 @@ class GameScene extends Phaser.Scene {
console.log('🌄 Initializing Parallax System...'); console.log('🌄 Initializing Parallax System...');
this.parallaxSystem = new ParallaxSystem(this); this.parallaxSystem = new ParallaxSystem(this);
// Generate Item Sprites for UI
TextureGenerator.createItemSprites(this);
// Launch UI Scene // Launch UI Scene
console.log('🖥️ Launching UI Scene...'); console.log('🖥️ Launching UI Scene...');
this.scene.launch('UIScene'); this.scene.launch('UIScene');
@@ -115,10 +125,10 @@ class GameScene extends Phaser.Scene {
// Initialize Save System // Initialize Save System
this.saveSystem = new SaveSystem(this); this.saveSystem = new SaveSystem(this);
// Auto-load if available (optional, for now manual) // Auto-load if available
// this.saveSystem.loadGame(); this.saveSystem.loadGame();
console.log('✅ GameScene ready - FAZA 17!'); console.log('✅ GameScene ready - FAZA 18 (Crafting & AI)!');
} }
setupCamera() { setupCamera() {
@@ -277,4 +287,12 @@ class GameScene extends Phaser.Scene {
this.clouds.push({ sprite: cloud, speed: Phaser.Math.FloatBetween(10, 30) }); this.clouds.push({ sprite: cloud, speed: Phaser.Math.FloatBetween(10, 30) });
} }
} }
saveGame() {
if (this.saveSystem) this.saveSystem.saveGame();
}
loadGame() {
if (this.saveSystem) this.saveSystem.loadGame();
}
} }

View File

@@ -20,9 +20,121 @@ class UIScene extends Phaser.Scene {
this.createGoldDisplay(); this.createGoldDisplay();
this.createClock(); this.createClock();
this.createDebugInfo(); this.createDebugInfo();
this.createSettingsButton();
// Resize event // Resize event
this.scale.on('resize', this.resize, this); this.scale.on('resize', this.resize, this);
// Crafting Menu (C)
this.input.keyboard.on('keydown-C', () => {
this.toggleCraftingMenu();
});
// Save (F5)
this.input.keyboard.on('keydown-F5', () => {
if (this.gameScene) this.gameScene.saveGame();
});
// Factory Reset (F8) - Fix za "zginla drevesa"
this.input.keyboard.on('keydown-F8', () => {
console.log('🔥 FACTORY RESET - Clearing Save & Reloading...');
localStorage.removeItem('novafarma_savefile');
window.location.reload();
});
}
// ... (rest of class) ...
toggleCraftingMenu() {
if (!this.craftingContainer) this.createCraftingMenu();
this.craftingContainer.setVisible(!this.craftingContainer.visible);
}
createCraftingMenu() {
const w = 300;
const h = 250;
const x = this.scale.width / 2;
const y = this.scale.height / 2;
this.craftingContainer = this.add.container(x, y);
this.craftingContainer.setDepth(2000); // Top of everything
// Bg
const bg = this.add.graphics();
bg.fillStyle(0x222222, 0.95);
bg.fillRect(-w / 2, -h / 2, w, h);
bg.lineStyle(2, 0x888888, 1);
bg.strokeRect(-w / 2, -h / 2, w, h);
this.craftingContainer.add(bg);
// Title
const title = this.add.text(0, -h / 2 + 20, 'CRAFTING', { fontSize: '24px', fontStyle: 'bold', color: '#ffffff' }).setOrigin(0.5);
this.craftingContainer.add(title);
// Recipes
const recipes = [
{ name: 'Axe', code: '1', req: '5 Wood', type: 'axe', cost: { wood: 5 } },
{ name: 'Pickaxe', code: '2', req: '5 Wood, 2 Stone', type: 'pickaxe', cost: { wood: 5, stone: 2 } },
{ name: 'Hoe', code: '3', req: '3 Wood, 2 Stone', type: 'hoe', cost: { wood: 3, stone: 2 } },
{ name: 'Sword', code: '4', req: '10 Wood, 5 Stone', type: 'sword', cost: { wood: 10, stone: 5 } }
];
recipes.forEach((r, i) => {
const rowY = -h / 2 + 70 + (i * 40);
// Text
const txt = this.add.text(-w / 2 + 20, rowY, `[${r.code}] ${r.name} (${r.req})`, {
fontSize: '16px', color: '#eeeeee'
});
this.craftingContainer.add(txt);
// Button Logic (Keyboard 1-4 works too via GameScene? No, let's localize input)
});
// Instruction
const instr = this.add.text(0, h / 2 - 20, 'Press number keys to craft', { fontSize: '12px', color: '#aaaaaa' }).setOrigin(0.5);
this.craftingContainer.add(instr);
// Input listener for crafting
this.input.keyboard.on('keydown', (e) => {
if (!this.craftingContainer.visible) return;
const key = e.key;
const recipe = recipes.find(r => r.code === key);
if (recipe) {
this.tryCraft(recipe);
}
});
this.craftingContainer.setVisible(false);
}
tryCraft(recipe) {
if (!this.gameScene || !this.gameScene.inventorySystem) return;
const inv = this.gameScene.inventorySystem;
// Check cost
if (recipe.cost.wood && !inv.hasItem('wood', recipe.cost.wood)) {
console.log('Craft fail: Wood');
return; // Add UI feedback "Need Wood"
}
if (recipe.cost.stone && !inv.hasItem('stone', recipe.cost.stone)) {
console.log('Craft fail: Stone');
return;
}
// Consume
if (recipe.cost.wood) inv.removeItem('wood', recipe.cost.wood);
if (recipe.cost.stone) inv.removeItem('stone', recipe.cost.stone);
// Add Item
inv.addItem(recipe.type, 1);
console.log(`Crafted ${recipe.name}!`);
// Flash effect
this.cameras.main.flash(200, 0, 255, 0); // Green flash
this.craftingContainer.setVisible(false);
} }
resize(gameSize) { resize(gameSize) {
@@ -35,7 +147,9 @@ class UIScene extends Phaser.Scene {
this.createClock(); this.createClock();
this.createGoldDisplay(); this.createGoldDisplay();
this.createInventoryBar(); this.createInventoryBar();
this.createInventoryBar();
this.createDebugInfo(); this.createDebugInfo();
this.createSettingsButton();
// Refresh data // Refresh data
if (this.gameScene) { if (this.gameScene) {
@@ -539,4 +653,113 @@ class UIScene extends Phaser.Scene {
this.tradeItemsContainer.add(btnBg); this.tradeItemsContainer.add(btnBg);
this.tradeItemsContainer.add(btnLabel); this.tradeItemsContainer.add(btnLabel);
} }
// --- SETTINGS MENU ---
createSettingsButton() {
if (this.settingsBtn) this.settingsBtn.destroy();
this.settingsBtn = this.add.text(10, 10, '⚙️ SETTINGS', {
fontSize: '16px',
fill: '#ffffff',
backgroundColor: '#000000',
padding: { x: 5, y: 5 }
});
this.settingsBtn.setInteractive({ useHandCursor: true });
this.settingsBtn.on('pointerdown', () => this.toggleSettingsMenu());
}
toggleSettingsMenu() {
if (!this.settingsContainer) {
this.createSettingsMenu();
}
this.settingsContainer.setVisible(!this.settingsContainer.visible);
if (this.settingsContainer.visible) {
this.settingsContainer.setDepth(20000); // Always on top
}
}
createSettingsMenu() {
this.settingsContainer = this.add.container(this.width / 2, this.height / 2);
// BG
const bg = this.add.graphics();
bg.fillStyle(0x000000, 0.9);
bg.fillRect(-150, -120, 300, 240);
bg.lineStyle(2, 0x888888, 1);
bg.strokeRect(-150, -120, 300, 240);
this.settingsContainer.add(bg);
// Title
const title = this.add.text(0, -90, 'SETTINGS', { fontSize: '24px', fontStyle: 'bold', fill: '#ffffff' }).setOrigin(0.5);
this.settingsContainer.add(title);
// Close
const closeBtn = this.add.text(130, -110, 'X', { fontSize: '20px', fill: '#ff0000' }).setOrigin(0.5);
closeBtn.setInteractive({ useHandCursor: true });
closeBtn.on('pointerdown', () => this.toggleSettingsMenu());
this.settingsContainer.add(closeBtn);
// Options
let y = -40;
// 1. View Distance (Simple Toggle for now: Low/High)
this.createSettingToggle(0, y, 'VIEW DISTANCE',
() => this.gameScene.settings.viewDistance, // getter
(val) => { // setter
this.gameScene.settings.viewDistance = val;
// Apply immediately? Or next frame.
// TerrainSystem reads it in updateCulling? We need to ensure it does.
if (this.gameScene.terrainSystem) this.gameScene.terrainSystem.lastCullX = -9999; // Force update
},
['LOW', 'HIGH']
);
y += 50;
// 2. Weather Particles
this.createSettingToggle(0, y, 'PARTICLES',
() => this.gameScene.settings.particles,
(val) => {
this.gameScene.settings.particles = val;
// Restart rain if active
if (this.gameScene.weatherSystem && (this.gameScene.weatherSystem.currentWeather === 'rain' || this.gameScene.weatherSystem.currentWeather === 'storm')) {
this.gameScene.weatherSystem.startRain(this.gameScene.weatherSystem.currentWeather === 'storm');
}
},
['NONE', 'LOW', 'HIGH']
);
y += 50;
// 3. Shadows
this.createSettingToggle(0, y, 'SHADOWS',
() => this.gameScene.settings.shadows ? 'ON' : 'OFF',
(val) => {
this.gameScene.settings.shadows = (val === 'ON');
// Trigger redraw? Complex. For now just saves state.
// Maybe reload scene?
},
['ON', 'OFF']
);
}
createSettingToggle(x, y, label, getter, setter, options) {
const labelText = this.add.text(x - 80, y, label, { fontSize: '16px', fill: '#aaaaaa' }).setOrigin(1, 0.5);
this.settingsContainer.add(labelText);
const currentVal = getter();
const valueText = this.add.text(x + 50, y, currentVal, { fontSize: '16px', fill: '#ffffff', fontStyle: 'bold' }).setOrigin(0.5, 0.5);
this.settingsContainer.add(valueText);
// Click to cycle
const hitArea = this.add.rectangle(x + 50, y, 100, 30, 0xffffff, 0.1);
hitArea.setInteractive({ useHandCursor: true });
hitArea.on('pointerdown', () => {
const cur = getter();
let idx = options.indexOf(cur);
idx = (idx + 1) % options.length;
const next = options[idx];
setter(next);
valueText.setText(next);
});
this.settingsContainer.add(hitArea);
}
} }

View File

@@ -18,6 +18,7 @@ class FarmingSystem {
// Let's say if it has crop and it is ripe, harvest it regardless of tool. // Let's say if it has crop and it is ripe, harvest it regardless of tool.
if (tile.hasCrop) { if (tile.hasCrop) {
const crop = terrain.cropsMap.get(`${gridX},${gridY}`); const crop = terrain.cropsMap.get(`${gridX},${gridY}`);
console.log('🌾 Check harvest:', crop);
if (crop && crop.stage === 4) { if (crop && crop.stage === 4) {
this.harvest(gridX, gridY); this.harvest(gridX, gridY);
return true; return true;
@@ -26,10 +27,11 @@ class FarmingSystem {
// 2. TILLING (Requires Hoe) // 2. TILLING (Requires Hoe)
if (toolType === 'hoe') { if (toolType === 'hoe') {
if (tile.type === 'grass' || tile.type === 'dirt') { const typeName = tile.type.name || tile.type;
if (typeName.includes('grass') || typeName === 'dirt') {
if (!tile.hasDecoration && !tile.hasCrop) { if (!tile.hasDecoration && !tile.hasCrop) {
console.log('🚜 Tilling soil...'); console.log('🚜 Tilling soil...');
terrain.setTileType(gridX, gridY, 'farmland'); terrain.setTileType(gridX, gridY, 'farmland'); // This sets it to string 'farmland' usually? or object? Assuming method handles it.
// Play sound // Play sound
return true; return true;
} }
@@ -38,10 +40,16 @@ class FarmingSystem {
// 3. PLANTING (Requires Seeds) // 3. PLANTING (Requires Seeds)
if (toolType === 'seeds') { if (toolType === 'seeds') {
if (tile.type === 'farmland' && !tile.hasCrop && !tile.hasDecoration) { const typeName = tile.type.name || tile.type;
if (typeName === 'farmland' && !tile.hasCrop && !tile.hasDecoration) {
console.log('🌱 Planting seeds...'); console.log('🌱 Planting seeds...');
this.plant(gridX, gridY); this.plant(gridX, gridY);
return true; // Consume seed logic handled by caller?
// Remove 1 seed from inventory
if (this.scene.inventorySystem) {
this.scene.inventorySystem.removeItem('seeds', 1);
}
return true;
} }
} }

View File

@@ -3,315 +3,228 @@ class InteractionSystem {
this.scene = scene; this.scene = scene;
this.iso = new IsometricUtils(48, 24); this.iso = new IsometricUtils(48, 24);
// Input listener setup (only once) // Input listener setup
this.scene.input.on('pointerdown', (pointer) => { this.scene.input.on('pointerdown', (pointer) => {
if (pointer.button === 0) { // Left Click if (pointer.button === 0) { // Left Click
this.handleLeftClick(pointer); const worldX = pointer.worldX - this.scene.terrainOffsetX;
const worldY = pointer.worldY - this.scene.terrainOffsetY;
const gridPos = this.iso.toGrid(worldX, worldY);
this.handleInteraction(gridPos.x, gridPos.y, false);
} }
}); });
// Key E listener (Easy Interaction/Tame)
this.scene.input.keyboard.on('keydown-E', () => {
this.handleInteractKey();
});
// Loot Array // Loot Array
this.drops = []; this.drops = [];
} }
handleLeftClick(pointer) { handleInteractKey() {
if (!this.scene.player) return; if (!this.scene.player || !this.scene.npcs) return;
// 1. Account for camera and offset
const worldX = pointer.worldX - this.scene.terrainOffsetX;
const worldY = pointer.worldY - this.scene.terrainOffsetY;
// 2. Convert to Grid
const gridPos = this.iso.toGrid(worldX, worldY);
// 3. Check distance
const playerPos = this.scene.player.getPosition(); const playerPos = this.scene.player.getPosition();
const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, gridPos.x, gridPos.y);
// Allow interaction within radius of 2.5 tiles // Find nearest NPC
if (dist > 2.5) { let nearest = null;
console.log('Too far:', dist.toFixed(1)); let minDist = 2.5; // Interaction range
return;
}
console.log(`☝️ Clicked tile: ${gridPos.x},${gridPos.y}`); for (const npc of this.scene.npcs) {
const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, npc.gridX, npc.gridY);
// DETERMINE TOOL / ACTION if (d < minDist) {
let activeTool = null; minDist = d;
const uiScene = this.scene.scene.get('UIScene'); nearest = npc;
const invSys = this.scene.inventorySystem;
if (uiScene && invSys) {
const selectedIdx = uiScene.selectedSlot;
const slotData = invSys.slots[selectedIdx];
if (slotData) activeTool = slotData.type;
}
// 0. Build Mode Override
if (this.scene.buildingSystem && this.scene.buildingSystem.isBuildMode) {
this.scene.buildingSystem.tryBuild(gridPos.x, gridPos.y);
return; // Consume click
}
// 3.5 Check for NPC Click
if (this.scene.npcs) {
for (const npc of this.scene.npcs) {
if (Math.abs(npc.gridX - gridPos.x) < 2.5 && Math.abs(npc.gridY - gridPos.y) < 2.5) {
console.log(`🗣️ Interact with NPC: ${npc.type}`);
if (npc.type === 'zombie') {
// Taming Logic
npc.toggleState();
return; // Done
}
if (npc.type === 'merchant') {
// Open Trade Menu
if (uiScene && invSys) {
uiScene.showTradeMenu(invSys);
}
return; // Stop processing
}
return; // Stop processing other clicks (farming/terrain) if clicked NPC
}
} }
} }
// 4. Try Farming Action (Tilling, Planting, Harvesting) if (nearest) {
if (this.scene.farmingSystem) { console.log('E Interacted with:', nearest.type);
const didFarm = this.scene.farmingSystem.interact(gridPos.x, gridPos.y, activeTool); if (nearest.type === 'zombie') {
if (didFarm) { // Always Tame on E key (Combat is Space/Click)
// Animation? nearest.tame();
return; } else {
} nearest.toggleState(); // Merchant/NPC talk
}
// 5. Try damage decoration
const id = `${gridPos.x},${gridPos.y}`;
if (this.scene.terrainSystem.decorationsMap.has(id)) {
const decor = this.scene.terrainSystem.decorationsMap.get(id);
// Calculate Damage based on Tool
let damage = 1; // Default hand damage
// Tool Logic
if (decor.type === 'tree' && activeTool === 'axe') {
damage = 3; // Axe destroys tree fast
} else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') {
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) { handleInteraction(gridX, gridY, isAttack = false) {
if (!this.scene.player) return; if (!this.scene.player) return;
// Check distance // 3. Check distance
const playerPos = this.scene.player.getPosition(); const playerPos = this.scene.player.getPosition();
const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, gridX, gridY); const dist = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, gridX, gridY);
if (dist > 3.0) { // Slightly increased radius for easier clicking // Allow interaction within radius of 2.5 tiles (1.5 for attack)
console.log('Too far:', dist.toFixed(1)); const maxDist = isAttack ? 1.5 : 2.5;
return; if (dist > maxDist) return;
}
// Get Active Tool // DETERMINE TOOL
let activeTool = null; let activeTool = isAttack ? 'sword' : null;
const uiScene = this.scene.scene.get('UIScene'); const uiScene = this.scene.scene.get('UIScene');
const invSys = this.scene.inventorySystem; const invSys = this.scene.inventorySystem;
if (uiScene && invSys) { if (uiScene && invSys && !isAttack) {
const selectedIdx = uiScene.selectedSlot; const selectedIdx = uiScene.selectedSlot;
const slotData = invSys.slots[selectedIdx]; const slotData = invSys.slots[selectedIdx];
if (slotData) activeTool = slotData.type; if (slotData) activeTool = slotData.type;
} }
// REUSE LOGIC if (isAttack && invSys) {
const selectedIdx = uiScene.selectedSlot;
const slotData = invSys.slots[selectedIdx];
if (slotData) activeTool = slotData.type;
}
// 0. Build Mode Override (Only click)
if (!isAttack && this.scene.buildingSystem && this.scene.buildingSystem.isBuildMode) {
this.scene.buildingSystem.tryBuild(gridX, gridY);
return;
}
// 3.5 Check for NPC Interaction
if (this.scene.npcs) {
for (const npc of this.scene.npcs) {
// Increased radius to 1.8 to catch moving NPCs easier
if (Math.abs(npc.gridX - gridX) < 1.8 && Math.abs(npc.gridY - gridY) < 1.8) {
if (npc.type === 'merchant' && !isAttack) {
if (uiScene && invSys) uiScene.showTradeMenu(invSys);
return;
}
if (npc.type === 'zombie') {
// Logic: Attack vs Tame
const isWeapon = activeTool === 'sword' || activeTool === 'axe' || activeTool === 'pickaxe';
if (isAttack || isWeapon) {
// COMBAT
let damage = 1;
if (activeTool === 'sword') damage = 5;
if (activeTool === 'axe') damage = 3;
if (activeTool === 'pickaxe') damage = 2;
if (npc.takeDamage) {
npc.takeDamage(damage);
}
return;
}
else {
// TAME ATTEMPT
console.log('🤝 Attempting to TAME zombie at', npc.gridX, npc.gridY);
npc.tame();
return;
}
}
if (!isAttack) npc.toggleState();
return;
}
}
}
// 4. Try Farming Action
if (this.scene.farmingSystem && !isAttack) {
const didFarm = this.scene.farmingSystem.interact(gridX, gridY, activeTool);
if (didFarm) return;
}
// 5. Try damage decoration
const id = `${gridX},${gridY}`; const id = `${gridX},${gridY}`;
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;
let damage = 1; // Default hand damage
// Tool Logic if (decor.type === 'tree') {
if (decor.type === 'tree' && activeTool === 'axe') { damage = (activeTool === 'axe') ? 3 : 1;
damage = 3; // Axe destroys tree fast if (!isAttack && activeTool !== 'axe') return;
} else if ((decor.type === 'bush' || decor.type === 'stone') && activeTool === 'pickaxe') {
damage = 3; // Pickaxe destroys stone fast
} }
else if (decor.type === 'stone') {
damage = (activeTool === 'pickaxe') ? 3 : 1;
if (!isAttack && activeTool !== 'pickaxe') return;
}
else if (decor.type === 'bush') damage = 2;
// Apply damage // Apply damage
const result = this.scene.terrainSystem.damageDecoration(gridX, gridY, damage); decor.hp -= damage;
this.showFloatingText(`${-damage}`, gridX, gridY, '#ffaaaa');
if (result === 'destroyed') { if (decor.hp <= 0) {
// Play proper sound const type = this.scene.terrainSystem.removeDecoration(gridX, gridY);
if (decor.type === 'tree') { // Loot logic
if (this.scene.soundManager) this.scene.soundManager.playChop(); let loot = 'wood';
} else { if (type === 'stone') loot = 'stone';
if (this.scene.soundManager) this.scene.soundManager.playChop(); if (type === 'bush') loot = 'seeds'; // Maybe berries?
if (type === 'tree') {
this.spawnLoot(gridX, gridY, 'wood');
this.spawnLoot(gridX, gridY, 'wood');
this.spawnLoot(gridX, gridY, 'wood');
}
else {
this.spawnLoot(gridX, gridY, loot);
} }
// AUTO-LOOT directly to Inventory } else {
let lootType = 'wood'; // Default // Shake visual
let lootCount = 1; const sprite = this.scene.terrainSystem.visibleDecorations.get(id);
if (sprite) {
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({ this.scene.tweens.add({
targets: txt, targets: sprite,
y: txt.y - 40, x: sprite.x + 2, yoyo: true, duration: 50, repeat: 2
alpha: 0,
duration: 1000,
onComplete: () => txt.destroy()
}); });
sprite.setTint(0xffaaaa);
this.scene.time.delayedCall(200, () => sprite.clearTint());
} }
} else if (result === 'hit') {
if (this.scene.soundManager) this.scene.soundManager.playChop();
} }
} }
} }
showFloatingText(text, gridX, gridY, color) {
const screenPos = this.iso.toScreen(gridX, gridY);
const txt = this.scene.add.text(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY - 30,
text,
{ fontSize: '14px', fill: color, stroke: '#000', strokeThickness: 2 }
).setOrigin(0.5);
this.scene.tweens.add({ targets: txt, y: txt.y - 40, alpha: 0, duration: 1000, onComplete: () => txt.destroy() });
}
spawnLoot(gridX, gridY, type) { spawnLoot(gridX, gridY, type) {
console.log(`🎁 Spawning ${type} at ${gridX},${gridY}`); console.log(`🎁 Spawning ${type} at ${gridX},${gridY}`);
// Convert to Screen
const screenPos = this.iso.toScreen(gridX, gridY); const screenPos = this.iso.toScreen(gridX, gridY);
const x = screenPos.x + this.scene.terrainOffsetX; const x = screenPos.x + this.scene.terrainOffsetX;
const y = screenPos.y + this.scene.terrainOffsetY; const y = screenPos.y + this.scene.terrainOffsetY;
// Create simplistic item drop sprite
let symbol = '?'; let symbol = '?';
if (type === 'wood') symbol = '🪵'; if (type === 'wood') symbol = '🪵';
if (type === 'stone') symbol = '🪨';
if (type === 'seeds') symbol = '🌱'; if (type === 'seeds') symbol = '🌱';
if (type === 'wheat') symbol = '🌾'; if (type === 'wheat') symbol = '🌾';
if (type === 'hoe') symbol = '🛠️'; if (type === 'axe') symbol = '🪓';
if (type === 'item_bone') symbol = '🦴';
const drop = this.scene.add.text(x, y - 20, symbol, { fontSize: '20px' }); const drop = this.scene.add.text(x, y - 20, symbol, { fontSize: '20px' });
drop.setOrigin(0.5); drop.setOrigin(0.5);
drop.setDepth(this.iso.getDepth(gridX, gridY) + 500); // above tiles drop.setDepth(this.iso.getDepth(gridX, gridY) + 500);
// Bounce animation
this.scene.tweens.add({ this.scene.tweens.add({
targets: drop, targets: drop, y: y - 40, duration: 500, yoyo: true, ease: 'Sine.easeOut', repeat: -1
y: y - 40,
duration: 500,
yoyo: true,
ease: 'Sine.easeOut',
repeat: -1
}); });
this.drops.push({ this.drops.push({ gridX, gridY, sprite: drop, type: type });
gridX,
gridY,
sprite: drop,
type: type
});
} }
update() { update() {
// Check for player pickup
if (!this.scene.player) return; if (!this.scene.player) return;
const playerPos = this.scene.player.getPosition(); const playerPos = this.scene.player.getPosition();
// Filter drops to pick up
for (let i = this.drops.length - 1; i >= 0; i--) { for (let i = this.drops.length - 1; i >= 0; i--) {
const drop = this.drops[i]; const drop = this.drops[i];
// Check if player is ON the drop tile
if (Math.abs(drop.gridX - playerPos.x) < 0.8 && Math.abs(drop.gridY - playerPos.y) < 0.8) { if (Math.abs(drop.gridX - playerPos.x) < 0.8 && Math.abs(drop.gridY - playerPos.y) < 0.8) {
// Pick up! if (this.scene.inventorySystem) this.scene.inventorySystem.addItem(drop.type, 1);
console.log('🎒 Picked up:', drop.type);
// Play pickup sound
if (this.scene.soundManager) this.scene.soundManager.playPickup();
// Add to inventory
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addItem(drop.type, 1);
}
// Destroy visual
drop.sprite.destroy(); drop.sprite.destroy();
this.drops.splice(i, 1); this.drops.splice(i, 1);
} }

View File

@@ -13,7 +13,6 @@ class TerrainSystem {
this.decorations = []; // Array za save/load compat this.decorations = []; // Array za save/load compat
this.decorationsMap = new Map(); // Fast lookup key->decor this.decorationsMap = new Map(); // Fast lookup key->decor
this.cropsMap = new Map(); // Store dynamic crops separately this.cropsMap = new Map(); // Store dynamic crops separately
// Render state monitoring // Render state monitoring
this.visibleTiles = new Map(); // Key: "x,y", Value: Sprite this.visibleTiles = new Map(); // Key: "x,y", Value: Sprite
this.visibleDecorations = new Map(); // Key: "x,y", Value: Sprite this.visibleDecorations = new Map(); // Key: "x,y", Value: Sprite
@@ -22,6 +21,13 @@ class TerrainSystem {
this.offsetX = 0; this.offsetX = 0;
this.offsetY = 0; this.offsetY = 0;
// Culling optimization
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 // Object Pools
this.tilePool = new ObjectPool( this.tilePool = new ObjectPool(
() => { () => {
@@ -251,6 +257,8 @@ class TerrainSystem {
// Zagotovi teksture // Zagotovi teksture
this.createTileTextures(); this.createTileTextures();
// DELETED Blitter Init
// Zagotovi decoration teksture - check for custom sprites first // Zagotovi decoration teksture - check for custom sprites first
if (!this.scene.textures.exists('flower')) { if (!this.scene.textures.exists('flower')) {
TextureGenerator.createFlowerSprite(this.scene, 'flower'); TextureGenerator.createFlowerSprite(this.scene, 'flower');
@@ -496,7 +504,7 @@ class TerrainSystem {
// Force Visual Update immediately? // Force Visual Update immediately?
// updateCulling will catch it on next frame, but to be safe: // updateCulling will catch it on next frame, but to be safe:
// Or leave it to update loop. this.lastCullX = -9999; // Force update
return true; return true;
} }
@@ -524,7 +532,9 @@ class TerrainSystem {
const key = `${x},${y}`; const key = `${x},${y}`;
this.cropsMap.set(key, cropData); 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 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) { removeCrop(x, y) {
@@ -558,14 +568,32 @@ class TerrainSystem {
// Update culling (called every frame) // Update culling (called every frame)
updateCulling(camera) { 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)
const view = camera.worldView; const view = camera.worldView;
const buffer = 200; // Optimization: Adjust buffer based on View Distance setting
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') {
buffer = 50;
}
const left = view.x - buffer - this.offsetX; const left = view.x - buffer - this.offsetX;
const top = view.y - buffer - this.offsetY; const top = view.y - buffer - this.offsetY;
const right = view.x + view.width + buffer - this.offsetX; const right = view.x + view.width + buffer - this.offsetX;
const bottom = view.y + view.height + buffer - this.offsetY; const bottom = view.y + view.height + buffer - this.offsetY;
// Calculate visible bounding box (rough)
const p1 = this.iso.toGrid(left, top); const p1 = this.iso.toGrid(left, top);
const p2 = this.iso.toGrid(right, top); const p2 = this.iso.toGrid(right, top);
const p3 = this.iso.toGrid(left, bottom); const p3 = this.iso.toGrid(left, bottom);
@@ -576,6 +604,12 @@ class TerrainSystem {
const minGridY = Math.floor(Math.min(p1.y, p2.y, p3.y, p4.y)); 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 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 startX = Math.max(0, minGridX);
const endX = Math.min(this.width, maxGridX); const endX = Math.min(this.width, maxGridX);
const startY = Math.max(0, minGridY); const startY = Math.max(0, minGridY);
@@ -587,118 +621,112 @@ class TerrainSystem {
for (let y = startY; y < endY; y++) { for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) { for (let x = startX; x < endX; x++) {
const key = `${x},${y}`; if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
neededKeys.add(key); const key = `${x},${y}`;
neededKeys.add(key);
// Tile Logic // Tile Logic
if (!this.visibleTiles.has(key)) { if (!this.visibleTiles.has(key)) {
const tilePos = this.iso.toScreen(x, y); const tilePos = this.iso.toScreen(x, y);
const tileData = this.tiles[y][x]; const tileData = this.tiles[y][x];
const sprite = this.tilePool.get(); // Get from Pool
sprite.setTexture(tileData.texture); const sprite = this.tilePool.get();
sprite.setTexture(tileData.texture);
// Elevation effect: MOČAN vertikalni offset za hribe // Elevation effect
const elevationOffset = tileData.elevation * -25; // Povečano iz -10 na -25 const elevationOffset = tileData.elevation * -25;
sprite.setPosition( sprite.setPosition(
tilePos.x + this.offsetX, tilePos.x + this.offsetX,
tilePos.y + this.offsetY + elevationOffset tilePos.y + this.offsetY + elevationOffset
); );
// DRAMATIČNO senčenje glede na višino // Senčenje
if (tileData.type === 'grass') { if (tileData.type.includes('grass')) {
let brightness = 1.0; let brightness = 1.0;
if (tileData.elevation > 0.5) {
if (tileData.elevation > 0.5) { brightness = 1.0 + (tileData.elevation - 0.5) * 1.0;
// Visoko = svetlo (1.0 - 1.5) } else {
brightness = 1.0 + (tileData.elevation - 0.5) * 1.0; 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))
));
} else { } else {
// Nizko = temno (0.7 - 1.0) sprite.clearTint();
brightness = 0.7 + tileData.elevation * 0.6;
} }
sprite.setTint(Phaser.Display.Color.GetColor( // FIXED DEPTH FOR DEBUG
Math.min(255, Math.floor(92 * brightness)), sprite.setDepth(-1000);
Math.min(255, Math.floor(184 * brightness)), sprite.setVisible(true);
Math.min(255, Math.floor(92 * brightness))
)); this.visibleTiles.set(key, sprite);
} }
sprite.setDepth(this.iso.getDepth(x, y) - 2000); // Tiles always in background // 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;
this.visibleTiles.set(key, sprite); 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);
// Elevation effect matching tile logic this.visibleCrops.set(key, sprite);
const tileData = this.tiles[y][x]; }
const elevationOffset = tileData.elevation * -25;
// 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 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 // Decoration Logic
if (this.tiles[y][x].hasDecoration) { if (this.tiles[y][x].hasDecoration) {
neededDecorKeys.add(key); neededDecorKeys.add(key);
if (!this.visibleDecorations.has(key)) { if (!this.visibleDecorations.has(key)) {
// Fast lookup from map // Fast lookup from map
const decor = this.decorationsMap.get(key); const decor = this.decorationsMap.get(key);
const tileData = this.tiles[y][x];
const elevationOffset = tileData.elevation * -25;
if (decor) { if (decor) {
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 // 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 + elevationOffset decorPos.y + this.offsetY + this.iso.tileHeight / 2 + elevationOffset
); );
const depth = this.iso.getDepth(x, y); const depth = this.iso.getDepth(x, y);
// Depth strategy: Base of object sorting. // Depth strategy: Base of object sorting.
// Add small offset based on type if needed, but mainly use Y // Add small offset based on type if needed, but mainly use Y
sprite.setDepth(depth); sprite.setDepth(depth);
// Apply scale if present // Apply scale if present
if (decor.scale) sprite.setScale(decor.scale); if (decor.scale) sprite.setScale(decor.scale);
else sprite.setScale(1); else sprite.setScale(1);
sprite.flipX = (x + y) % 2 === 0; sprite.flipX = (x + y) % 2 === 0;
// INTERACTIVITY FIX: Allow clicking sprites directly // Sprites are just visual now, interaction handled by InteractionSystem via grid
sprite.setInteractive({ pixelPerfect: true, useHandCursor: true }); // sprite.setInteractive(...) removed to fix conflicts
// Clear old listeners this.visibleDecorations.set(key, sprite);
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);
} }
} }
} }

View File

@@ -11,7 +11,8 @@ class WeatherSystem {
this.currentWeather = 'clear'; this.currentWeather = 'clear';
this.weatherDuration = 0; this.weatherDuration = 0;
this.maxWeatherDuration = 10000; // Random duration logic handles this this.maxWeatherDuration = 10000; // Random duration logic handles this
this.rainParticles = []; // {x, y, speed, length} this.rainEmitter = null; // Replaced manual Array with Emitter
// --- State --- // --- State ---
this.currentPhase = 'day'; this.currentPhase = 'day';
@@ -90,20 +91,7 @@ class WeatherSystem {
} }
updateWeatherPhysics(delta) { updateWeatherPhysics(delta) {
if (this.currentWeather === 'rain' || this.currentWeather === 'storm') { // Optimisation: Physics now handled by Phaser Particles
const width = this.scene.scale.width;
const height = this.scene.scale.height;
for (const drop of this.rainParticles) {
drop.y += (drop.speed * delta) / 1000;
// Wrap around
if (drop.y > height) {
drop.y = -10;
drop.x = Math.random() * width;
}
}
}
} }
render() { render() {
@@ -168,14 +156,7 @@ class WeatherSystem {
overlay.fillStyle(0x000033, isDark); overlay.fillStyle(0x000033, isDark);
overlay.fillRect(0, 0, width, height); overlay.fillRect(0, 0, width, height);
// Draw drops // Rain drops are now particles (separate GameObject)
overlay.lineStyle(1, 0x88aaff, 0.5);
for (const drop of this.rainParticles) {
overlay.beginPath();
overlay.moveTo(drop.x, drop.y);
overlay.lineTo(drop.x - 2, drop.y + drop.length);
overlay.strokePath();
}
} }
} }
@@ -204,22 +185,61 @@ class WeatherSystem {
} }
startRain(heavy) { startRain(heavy) {
const width = this.scene.scale.width; const uiScene = this.scene.scene.get('UIScene');
const height = this.scene.scale.height; if (!uiScene) return;
const dropCount = heavy ? 150 : 100;
for (let i = 0; i < dropCount; i++) { // Check Settings
this.rainParticles.push({ const quality = this.scene.settings ? this.scene.settings.particles : 'HIGH';
x: Math.random() * width, if (quality === 'NONE') return;
y: Math.random() * height,
speed: heavy ? Phaser.Math.Between(400, 600) : Phaser.Math.Between(200, 400), // Ensure texture exists
length: heavy ? Phaser.Math.Between(10, 15) : Phaser.Math.Between(5, 10) if (!this.scene.textures.exists('rain_drop')) {
}); const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
graphics.fillStyle(0x88aaff, 1);
graphics.fillRect(0, 0, 2, 8);
graphics.generateTexture('rain_drop', 2, 8);
graphics.destroy();
} }
// Clean up old
if (this.rainEmitter) {
this.rainEmitter.destroy();
}
const width = this.scene.scale.width;
let pQuantity = heavy ? 5 : 2;
let pFreq = heavy ? 10 : 30;
if (quality === 'LOW') {
pQuantity = heavy ? 2 : 1;
pFreq = heavy ? 50 : 100;
}
// Use modern particles or fallback
// Helper to support both if needed, but standard 3.60+ is:
this.rainEmitter = uiScene.add.particles(0, 0, 'rain_drop', {
x: { min: 0, max: width },
y: -20,
quantity: pQuantity,
frequency: pFreq, // Emit every X ms
lifespan: 1500,
speedY: { min: heavy ? 600 : 400, max: heavy ? 900 : 600 },
speedX: { min: -50, max: 0 }, // Slight wind
scaleY: { min: 1.0, max: 2.0 },
alpha: { start: 0.6, end: 0 },
emitting: true
});
// Depth just above overlay (-1000)
this.rainEmitter.setDepth(-990);
} }
clearWeather() { clearWeather() {
this.rainParticles = []; if (this.rainEmitter) {
this.rainEmitter.destroy();
this.rainEmitter = null;
}
} }
// --- Getters for Other Systems --- // --- Getters for Other Systems ---

View File

@@ -577,27 +577,6 @@ class TextureGenerator {
// Left Wall (Broken) // Left Wall (Broken)
ctx.fillStyle = '#555555'; // Dark Grey ctx.fillStyle = '#555555'; // Dark Grey
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(32, 60);
ctx.lineTo(10, 50);
ctx.lineTo(10, 40); // Lower than house
ctx.lineTo(20, 45); // Jagged
ctx.lineTo(25, 38);
ctx.lineTo(32, 45);
ctx.fill();
// Right Wall (Broken)
ctx.fillStyle = '#777777'; // Light Grey
ctx.beginPath();
ctx.moveTo(32, 60);
ctx.lineTo(54, 50);
ctx.lineTo(54, 35);
ctx.lineTo(45, 30);
ctx.lineTo(40, 35);
ctx.lineTo(32, 25); // Exposed interior?
ctx.lineTo(32, 60);
ctx.fill();
// Debris piles
ctx.fillStyle = '#333333'; ctx.fillStyle = '#333333';
ctx.beginPath(); // Pile 1 ctx.beginPath(); // Pile 1
ctx.arc(20, 55, 5, 0, Math.PI * 2); ctx.arc(20, 55, 5, 0, Math.PI * 2);
@@ -831,4 +810,117 @@ class TextureGenerator {
canvas.refresh(); canvas.refresh();
} }
} }
// Generiraj vse ikone za items
static createItemSprites(scene) {
// 1. AXE
this.createToolSprites(scene);
// 2. PICKAXE
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, 12, 4, 18);
// Head (Pick)
ctx.fillStyle = '#C0C0C0'; // Silver
ctx.beginPath();
ctx.moveTo(16, 12);
ctx.quadraticCurveTo(28, 8, 30, 16); // Right curve
ctx.lineTo(26, 18); // Sharp point right
ctx.lineTo(16, 14); // Center
ctx.lineTo(6, 18); // Sharp point left
ctx.lineTo(2, 16); // Left curve tip
ctx.quadraticCurveTo(4, 8, 16, 12);
ctx.fill();
canvas.refresh();
}
// 3. HOE
if (!scene.textures.exists('item_hoe')) {
const size = 32;
const canvas = scene.textures.createCanvas('item_hoe', size, size);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, size, size);
// Handle
ctx.fillStyle = '#8B4513';
ctx.fillRect(14, 4, 4, 26);
// Head
ctx.fillStyle = '#C0C0C0';
ctx.fillRect(6, 4, 12, 4); // Top bar
ctx.fillRect(6, 4, 4, 8); // Down blade
canvas.refresh();
}
// 4. STONE
if (!scene.textures.exists('item_stone')) {
const size = 32;
const canvas = scene.textures.createCanvas('item_stone', size, size);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, size, size);
ctx.fillStyle = '#808080'; // Grey
ctx.beginPath();
ctx.arc(16, 16, 10, 0, Math.PI * 2);
ctx.fill();
// Shading
ctx.fillStyle = '#A9A9A9';
ctx.beginPath();
ctx.arc(12, 12, 4, 0, Math.PI * 2);
ctx.fill();
// Crack
ctx.strokeStyle = '#555555';
ctx.beginPath();
ctx.moveTo(16, 16);
ctx.lineTo(20, 20);
ctx.stroke();
canvas.refresh();
}
// 5. WOOD
if (!scene.textures.exists('item_wood')) {
const size = 32;
const canvas = scene.textures.createCanvas('item_wood', size, size);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, size, size);
// Log
ctx.fillStyle = '#8B4513';
ctx.fillRect(8, 12, 16, 8);
ctx.fillStyle = '#A0522D'; // Lighter finish
ctx.fillRect(24, 12, 4, 8); // End cap
ctx.fillStyle = '#D2691E'; // Bark details
ctx.fillRect(10, 14, 8, 2);
canvas.refresh();
}
// 6. SEEDS
if (!scene.textures.exists('item_seeds')) {
const size = 32;
const canvas = scene.textures.createCanvas('item_seeds', size, size);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, size, size);
ctx.fillStyle = '#DEB887'; // Burlywood
// 3 seeds
ctx.beginPath(); ctx.arc(12, 16, 3, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(20, 14, 3, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(16, 20, 3, 0, Math.PI * 2); ctx.fill();
canvas.refresh();
}
}
} }