This commit is contained in:
2025-12-08 10:47:53 +01:00
parent 5de27d0b04
commit b79b70dcc1
11 changed files with 1001 additions and 21 deletions

153
src/entities/Scooter.js Normal file
View File

@@ -0,0 +1,153 @@
class Scooter {
constructor(scene, gridX, gridY) {
this.scene = scene;
this.gridX = gridX;
this.gridY = gridY;
this.iso = new IsometricUtils(48, 24); // Assuming global availability or import if needed, but classes usually have access if loaded.
// Actually Scene has this.iso, better use that.
this.isBroken = true;
this.isMounted = false;
this.type = 'scooter'; // For interaction checks
this.createSprite();
}
createSprite() {
if (this.sprite) this.sprite.destroy();
const tex = this.isBroken ? 'scooter_broken' : 'scooter';
const screenPos = this.scene.iso.toScreen(this.gridX, this.gridY);
this.sprite = this.scene.add.sprite(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY,
tex
);
this.sprite.setOrigin(0.5, 1);
this.updateDepth();
}
updateDepth() {
if (this.sprite) {
const layerBase = this.scene.iso.LAYER_OBJECTS || 200000;
this.sprite.setDepth(layerBase + this.sprite.y);
}
}
interact(player) {
if (this.isBroken) {
this.tryFix(player);
} else {
this.toggleRide(player);
}
}
tryFix(player) {
// Logic: Check if player has tools?
// User said: "ga more popraviti" (needs to fix it).
// Let's just require a short delay or check for 'wrench' if we had one.
// For easter egg, let's say hitting it with a hammer works, or just interacting.
// Let's make it simple: "Fixing Scooter..." progress.
console.log('🔧 Fixing Scooter...');
this.scene.events.emit('show-floating-text', {
x: this.sprite.x,
y: this.sprite.y - 50,
text: "Fixing...",
color: '#FFFF00'
});
// Plays sound
if (this.scene.soundManager) this.scene.soundManager.playHit(); // Clank sounds
// Delay 2 seconds then fix
this.scene.time.delayedCall(2000, () => {
this.isBroken = false;
this.createSprite(); // Update texture to shiny
this.scene.events.emit('show-floating-text', {
x: this.sprite.x,
y: this.sprite.y - 50,
text: "Scooter Fixed!",
color: '#00FF00'
});
console.log('✅ Scooter Fixed!');
});
}
toggleRide(player) {
if (this.isMounted) {
this.dismount(player);
} else {
this.mount(player);
}
}
mount(player) {
if (!player) return;
this.isMounted = true;
this.sprite.setVisible(false);
// Boost player speed
this.originalSpeed = player.moveSpeed;
this.originalMoveTime = player.gridMoveTime;
player.gridMoveTime = 100; // Faster (was 200)
// Attach a visual indicator to player?
// Ideally we'd change player sprite, but we don't have 'player_scooter'.
// We can create a "Scooter Attachment" sprite in Player, or just assume he's on it.
// Let's update Player to have a "vehicle" property.
player.vehicle = this;
this.scene.events.emit('show-floating-text', {
x: player.sprite.x,
y: player.sprite.y - 50,
text: "Riding Scooter!",
color: '#00FFFF'
});
}
dismount(player) {
if (!player) return;
this.isMounted = false;
// Reset player stats
player.gridMoveTime = this.originalMoveTime || 200;
player.vehicle = null;
// Place scooter at player's current position
this.gridX = player.gridX;
this.gridY = player.gridY;
const screenPos = this.scene.iso.toScreen(this.gridX, this.gridY);
this.sprite.setPosition(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY
);
this.sprite.setVisible(true);
this.updateDepth();
this.scene.events.emit('show-floating-text', {
x: player.sprite.x,
y: player.sprite.y - 50,
text: "Dismounted",
color: '#CCCCCC'
});
}
update() {
if (this.isMounted && this.scene.player) {
// Keep grid position synced with player for logic, even if invisible
this.gridX = this.scene.player.gridX;
this.gridY = this.scene.player.gridY;
// Also update sprite pos just in case
const screenPos = this.scene.iso.toScreen(this.gridX, this.gridY);
this.sprite.setPosition(
screenPos.x + this.scene.terrainOffsetX,
screenPos.y + this.scene.terrainOffsetY
);
}
}
}

View File

@@ -158,14 +158,18 @@ class GameScene extends Phaser.Scene {
}
*/
// ELITE ZOMBIES v City območju (65,65 ± 15)
console.log('👹 Spawning ELITE ZOMBIES in City...');
for (let i = 0; i < 15; i++) { // Veliko elite zombijev!
const randomX = Phaser.Math.Between(50, 80); // City area
const randomY = Phaser.Math.Between(50, 80);
const elite = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, 'elite_zombie');
this.npcs.push(elite);
}
// ELITE ZOMBIE v City območju (samo 1 za testiranje)
console.log('👹 Spawning ELITE ZOMBIE in City...');
const eliteX = Phaser.Math.Between(50, 80); // City area
const eliteY = Phaser.Math.Between(50, 80);
const elite = new NPC(this, eliteX, eliteY, this.terrainOffsetX, this.terrainOffsetY, 'elite_zombie');
this.npcs.push(elite);
// Easter Egg: Broken Scooter
console.log('🛵 Spawning Scooter Easter Egg...');
this.vehicles = [];
const scooter = new Scooter(this, 25, 25);
this.vehicles.push(scooter);
// Kamera sledi igralcu z gladko interpolacijo (lerp 0.1)
this.cameras.main.startFollow(this.player.sprite, true, 0.1, 0.1);
@@ -342,6 +346,13 @@ class GameScene extends Phaser.Scene {
npc.update(delta);
}
// Vehicles Update
if (this.vehicles) {
for (const vehicle of this.vehicles) {
if (vehicle.update) vehicle.update(delta);
}
}
// Parallax
if (this.parallaxSystem && this.player) {
const playerPos = this.player.getPosition();

View File

@@ -78,6 +78,17 @@ class FarmingSystem {
}
}
// 4. WATERING
if (toolType === 'watering_can') {
if (tile.hasCrop) {
const crop = terrain.cropsMap.get(`${gridX},${gridY}`);
if (crop && !crop.isWatered) {
this.waterCrop(gridX, gridY);
return true;
}
}
}
return false;
}
@@ -139,6 +150,7 @@ class FarmingSystem {
if (data.regrow) {
crop.stage = data.regrowStage;
crop.timer = 0;
crop.isWatered = false; // Reset watering status
terrain.updateCropVisual(x, y, crop.stage);
console.log(`🔄 ${crop.type} regrowing...`);
} else {
@@ -146,6 +158,37 @@ class FarmingSystem {
}
}
waterCrop(x, y) {
const terrain = this.scene.terrainSystem;
const crop = terrain.cropsMap.get(`${x},${y}`);
if (!crop) return;
crop.isWatered = true;
crop.growthBoost = 2.0; // 2x faster growth!
// Visual feedback - tint slightly blue
const key = `${x},${y}`;
if (terrain.visibleCrops.has(key)) {
const sprite = terrain.visibleCrops.get(key);
sprite.setTint(0xAADDFF); // Light blue tint
}
// Sound effect
if (this.scene.soundManager) {
this.scene.soundManager.playPlant(); // Re-use plant sound for now
}
// Floating text
this.scene.events.emit('show-floating-text', {
x: x * 48,
y: y * 48,
text: '💧 Watered!',
color: '#00AAFF'
});
console.log(`💧 Watered crop at ${x},${y}`);
}
update(delta) {
this.growthTimer += delta;
if (this.growthTimer < 1000) return;
@@ -160,10 +203,26 @@ class FarmingSystem {
if (!data) continue;
if (crop.stage < data.stages) {
crop.timer += secondsPassed;
// Apply growth boost if watered
const growthMultiplier = crop.growthBoost || 1.0;
crop.timer += secondsPassed * growthMultiplier;
if (crop.timer >= data.growthTime) {
crop.stage++;
crop.timer = 0;
// Clear watering boost after growth
if (crop.isWatered) {
crop.isWatered = false;
crop.growthBoost = 1.0;
// Remove blue tint
if (terrain.visibleCrops.has(key)) {
const sprite = terrain.visibleCrops.get(key);
sprite.clearTint();
}
}
terrain.updateCropVisual(crop.gridX, crop.gridY, crop.stage);
}
}

View File

@@ -29,6 +29,16 @@ class InteractionSystem {
const candidates = this.scene.spatialGrid ? this.scene.spatialGrid.query(playerPos.x, playerPos.y) : this.scene.npcs;
if (this.scene.vehicles) {
for (const v of this.scene.vehicles) {
const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, v.gridX, v.gridY);
if (d < minDist) {
minDist = d;
nearest = v;
}
}
}
for (const npc of candidates) {
const d = Phaser.Math.Distance.Between(playerPos.x, playerPos.y, npc.gridX, npc.gridY);
if (d < minDist) {
@@ -39,7 +49,10 @@ class InteractionSystem {
if (nearest) {
console.log('E Interacted with:', nearest.type);
if (nearest.type === 'zombie') {
if (nearest.type === 'scooter') {
nearest.interact(this.scene.player);
}
else if (nearest.type === 'zombie') {
// Always Tame on E key (Combat is Space/Click)
nearest.tame();
} else {
@@ -82,6 +95,20 @@ class InteractionSystem {
return;
}
// 3.4 Check for Vehicles (Scooter)
if (!isAttack && this.scene.vehicles) {
for (const vehicle of this.scene.vehicles) {
// If mounted, dismount (interact with self/vehicle at same pos)
// If unmounted, check proximity
if (Math.abs(vehicle.gridX - gridX) < 1.5 && Math.abs(vehicle.gridY - gridY) < 1.5) {
if (vehicle.interact) {
vehicle.interact(this.scene.player);
return; // Stop other interactions
}
}
}
}
// 3.5 Check for NPC Interaction
const candidates = this.scene.spatialGrid ? this.scene.spatialGrid.query(gridX, gridY) : this.scene.npcs;

View File

@@ -16,6 +16,7 @@ class InventorySystem {
this.addItem('axe', 1); // 🪓 Sekira
this.addItem('pickaxe', 1); // ⛏️ Kramp
this.addItem('hoe', 1);
this.addItem('watering_can', 1); // 💧 Zalivalka
this.addItem('seeds', 5); // Zmanjšano število semen
// Removed default wood/stone so player has to gather them

View File

@@ -305,14 +305,73 @@ class TerrainSystem {
}
}
// DECORATIONS REMOVED BY REQUEST
// Drevesa, kamni, rože in ruševine so odstranjeni.
// DECORATIONS - Enhanced World Details
console.log('🌸 Adding enhanced decorations...');
// Ostalo je samo generiranje ploščic (tiles) in fixnih con (farm, city floor).
// Natural Path Stones (along roads and random areas)
let pathStoneCount = 0;
for (let i = 0; i < 50; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.addDecoration(pos.x, pos.y, 'path_stone');
pathStoneCount++;
}
}
console.log(`✅ Teren generiran (CLEAN): ${treeCount} dreves, ${rockCount} kamnov.`);
// Small decorative rocks
let smallRockCount = 0;
for (let i = 0; i < 80; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const rockType = Math.random() > 0.5 ? 'small_rock_1' : 'small_rock_2';
this.addDecoration(pos.x, pos.y, rockType);
smallRockCount++;
}
}
console.log(`✅ Teren generiran: ${treeCount} dreves, ${rockCount} kamnov.`);
// Flower clusters
flowerCount = 0;
for (let i = 0; i < 100; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const flowers = ['flower_red', 'flower_yellow', 'flower_blue'];
const flowerType = flowers[Math.floor(Math.random() * flowers.length)];
this.addDecoration(pos.x, pos.y, flowerType);
flowerCount++;
}
}
// Mushrooms (spooky atmosphere)
let mushroomCount = 0;
for (let i = 0; i < 60; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
const mushroomType = Math.random() > 0.5 ? 'mushroom_red' : 'mushroom_brown';
this.addDecoration(pos.x, pos.y, mushroomType);
mushroomCount++;
}
}
// Fallen Logs (forest debris)
let logCount = 0;
for (let i = 0; i < 25; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.addDecoration(pos.x, pos.y, 'fallen_log');
logCount++;
}
}
// Puddles (will appear during rain)
this.puddlePositions = [];
for (let i = 0; i < 40; i++) {
const pos = validPositions[Math.floor(Math.random() * validPositions.length)];
if (pos && !this.decorationsMap.has(`${pos.x},${pos.y}`)) {
this.puddlePositions.push({ x: pos.x, y: pos.y });
}
}
console.log(`✅ Decorations: ${pathStoneCount} paths, ${smallRockCount} rocks, ${flowerCount} flowers, ${mushroomCount} mushrooms, ${logCount} logs.`);
}
damageDecoration(x, y, amount) {
@@ -534,7 +593,16 @@ class TerrainSystem {
// Determine if decoration is SOLID (blocking movement)
const typeLower = type.toLowerCase();
const isSolid = typeLower.includes('tree') ||
// Small decorations are NOT solid (can walk through)
const isSmallDecor = typeLower.includes('flower') ||
typeLower.includes('small_rock') ||
typeLower.includes('path_stone') ||
typeLower.includes('mushroom') ||
typeLower.includes('puddle');
const isSolid = !isSmallDecor && (
typeLower.includes('tree') ||
typeLower.includes('sapling') ||
typeLower.includes('rock') ||
typeLower.includes('stone') ||
@@ -548,7 +616,9 @@ class TerrainSystem {
typeLower.includes('arena') ||
typeLower.includes('house') ||
typeLower.includes('gravestone') ||
typeLower.includes('bush');
typeLower.includes('bush') ||
typeLower.includes('fallen_log')
);
const decorData = {
gridX: gridX,
@@ -655,7 +725,22 @@ class TerrainSystem {
neededTileKeys.add(key);
if (!this.visibleTiles.has(key)) {
const sprite = this.tilePool.get();
sprite.setTexture(tile.type);
// Use water texture with animation support
if (tile.type === 'water') {
// Check if water frames exist
if (this.scene.textures.exists('water_frame_0')) {
sprite.setTexture('water_frame_0');
// Mark sprite for animation
sprite.isWater = true;
sprite.waterFrame = 0;
} else {
sprite.setTexture('water');
}
} else {
sprite.setTexture(tile.type);
}
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY));
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_FLOOR)); // Tiles = Floor
@@ -751,6 +836,20 @@ class TerrainSystem {
}
update(delta) {
// Water animation (250ms per frame = 4 FPS)
this.waterAnimTimer = (this.waterAnimTimer || 0) + delta;
if (this.waterAnimTimer > 250) {
this.waterAnimTimer = 0;
this.waterCurrentFrame = ((this.waterCurrentFrame || 0) + 1) % 4;
// Update all water tiles
for (const [key, sprite] of this.visibleTiles) {
if (sprite.isWater) {
sprite.setTexture(`water_frame_${this.waterCurrentFrame}`);
}
}
}
this.growthTimer = (this.growthTimer || 0) + delta;
if (this.growthTimer < 5000) return;
this.growthTimer = 0;

View File

@@ -648,6 +648,323 @@ class TextureGenerator {
TextureGenerator.createGravestoneSprite(this.scene);
TextureGenerator.createToolSprites(this.scene);
TextureGenerator.createItemSprites(this.scene);
TextureGenerator.createScooterSprite(this.scene, 'scooter', false);
TextureGenerator.createScooterSprite(this.scene, 'scooter_broken', true);
TextureGenerator.createAnimatedWaterSprite(this.scene);
TextureGenerator.createPathStoneSprite(this.scene, 'path_stone');
TextureGenerator.createSmallRockSprite(this.scene, 'small_rock_1');
TextureGenerator.createSmallRockSprite(this.scene, 'small_rock_2');
TextureGenerator.createFlowerVariant(this.scene, 'flower_red', '#FF4444');
TextureGenerator.createFlowerVariant(this.scene, 'flower_yellow', '#FFFF44');
TextureGenerator.createFlowerVariant(this.scene, 'flower_blue', '#4444FF');
TextureGenerator.createMushroomSprite(this.scene, 'mushroom_red', '#FF4444', '#FFFFFF');
TextureGenerator.createMushroomSprite(this.scene, 'mushroom_brown', '#8B4513', '#FFE4C4');
TextureGenerator.createFallenLogSprite(this.scene, 'fallen_log');
TextureGenerator.createPuddleSprite(this.scene, 'puddle');
}
static createPathStoneSprite(scene, key = 'path_stone') {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
// Flat stone for paths
ctx.fillStyle = '#888888';
ctx.beginPath();
ctx.ellipse(16, 20, 12, 8, 0, 0, Math.PI * 2);
ctx.fill();
// Cracks/Details
ctx.strokeStyle = '#666666';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(10, 18);
ctx.lineTo(22, 18);
ctx.stroke();
canvas.refresh();
}
static createSmallRockSprite(scene, key) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 24, 24);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 24, 24);
// Small rock pile
ctx.fillStyle = '#707070';
ctx.beginPath();
ctx.arc(12, 16, 6, 0, Math.PI * 2);
ctx.fill();
// Highlight
ctx.fillStyle = '#909090';
ctx.beginPath();
ctx.arc(10, 14, 3, 0, Math.PI * 2);
ctx.fill();
canvas.refresh();
}
static createFlowerVariant(scene, key, color) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
// Stem
ctx.fillStyle = '#228B22';
ctx.fillRect(15, 16, 2, 10);
// Petals
ctx.fillStyle = color;
for (let i = 0; i < 5; i++) {
const angle = (i / 5) * Math.PI * 2;
const px = 16 + Math.cos(angle) * 4;
const py = 16 + Math.sin(angle) * 4;
ctx.beginPath();
ctx.arc(px, py, 3, 0, Math.PI * 2);
ctx.fill();
}
// Center
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(16, 16, 2, 0, Math.PI * 2);
ctx.fill();
canvas.refresh();
}
static createMushroomSprite(scene, key, capColor, stemColor) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 32);
// Stem
ctx.fillStyle = stemColor;
ctx.fillRect(13, 16, 6, 10);
// Cap (mushroom head)
ctx.fillStyle = capColor;
ctx.beginPath();
ctx.ellipse(16, 16, 10, 6, 0, 0, Math.PI * 2);
ctx.fill();
// Spots on cap (white dots)
ctx.fillStyle = '#FFFFFF';
for (let i = 0; i < 3; i++) {
const angle = (i / 3) * Math.PI * 2;
const px = 16 + Math.cos(angle) * 5;
const py = 16 + Math.sin(angle) * 3;
ctx.beginPath();
ctx.arc(px, py, 2, 0, Math.PI * 2);
ctx.fill();
}
canvas.refresh();
}
static createFallenLogSprite(scene, key = 'fallen_log') {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 48, 32);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 48, 32);
// Log body (horizontal)
ctx.fillStyle = '#8B4513';
ctx.fillRect(4, 14, 40, 10);
// Bark texture
ctx.fillStyle = '#654321';
for (let i = 0; i < 5; i++) {
ctx.fillRect(6 + i * 8, 14, 2, 10);
}
// Log rings (ends)
ctx.fillStyle = '#D2691E';
ctx.beginPath();
ctx.arc(6, 19, 5, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#654321';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(6, 19, 3, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(6, 19, 1.5, 0, Math.PI * 2);
ctx.stroke();
// Small mushrooms growing on log
ctx.fillStyle = '#FF6347';
ctx.beginPath();
ctx.arc(20, 12, 3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFE4C4';
ctx.fillRect(19, 13, 2, 3);
canvas.refresh();
}
static createPuddleSprite(scene, key = 'puddle') {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 32, 24);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 32, 24);
// Puddle shape (irregular oval)
ctx.fillStyle = 'rgba(70, 130, 180, 0.6)';
ctx.beginPath();
ctx.ellipse(16, 16, 12, 8, 0, 0, Math.PI * 2);
ctx.fill();
// Reflection highlights
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.ellipse(12, 14, 4, 2, 0, 0, Math.PI * 2);
ctx.fill();
// Darker edge
ctx.strokeStyle = 'rgba(30, 60, 90, 0.5)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.ellipse(16, 16, 12, 8, 0, 0, Math.PI * 2);
ctx.stroke();
canvas.refresh();
}
static createScooterSprite(scene, key = 'scooter', isBroken = false) {
if (scene.textures.exists(key)) return;
const canvas = scene.textures.createCanvas(key, 48, 48);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, 48, 48);
const color = isBroken ? '#696969' : '#FF4500'; // Rusty Gray vs OrangeRed
const wheelColor = '#1a1a1a';
// Wheels
ctx.fillStyle = wheelColor;
ctx.beginPath(); ctx.arc(12, 38, 5, 0, Math.PI * 2); ctx.fill(); // Back
ctx.beginPath(); ctx.arc(38, 38, 5, 0, Math.PI * 2); ctx.fill(); // Front
// Spokes
ctx.fillStyle = '#555';
ctx.beginPath(); ctx.arc(12, 38, 2, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(38, 38, 2, 0, Math.PI * 2); ctx.fill();
// Base
ctx.fillStyle = color;
ctx.fillRect(12, 32, 26, 5); // Deck
// Angled Stem
ctx.beginPath();
ctx.moveTo(38, 34);
ctx.lineTo(34, 14);
ctx.lineTo(38, 14);
ctx.lineTo(42, 34);
ctx.closePath();
ctx.fill();
// Handlebars
ctx.strokeStyle = isBroken ? '#444' : '#C0C0C0';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(30, 14);
ctx.lineTo(42, 14);
ctx.stroke();
// Vertical post support
ctx.fillStyle = color;
ctx.fillRect(10, 30, 4, 4); // Rear wheel guard
if (isBroken) {
// Rust spots
ctx.fillStyle = '#8B4513';
ctx.fillRect(15, 33, 4, 2);
ctx.fillRect(35, 20, 2, 3);
}
canvas.refresh();
}
static createAnimatedWaterSprite(scene) {
// Create 4 separate textures for water animation frames
const frames = 4;
const frameWidth = 48;
const frameHeight = 60;
for (let f = 0; f < frames; f++) {
const key = `water_frame_${f}`;
if (scene.textures.exists(key)) continue;
const canvas = scene.textures.createCanvas(key, frameWidth, frameHeight);
const ctx = canvas.getContext();
ctx.clearRect(0, 0, frameWidth, frameHeight);
const P = 2;
const baseColor = 0x4444ff;
const shimmerOffset = Math.sin((f / frames) * Math.PI * 2) * 20;
const waterColor = Phaser.Display.Color.IntegerToColor(baseColor).lighten(shimmerOffset).color;
const xs = P;
const xe = 48 + P;
const midX = 24 + P;
const topY = P;
const midY = 12 + P;
const bottomY = 24 + P;
const depth = 20;
// Left Face
ctx.fillStyle = Phaser.Display.Color.IntegerToColor(waterColor).darken(30).rgba;
ctx.beginPath();
ctx.moveTo(midX, bottomY);
ctx.lineTo(midX, bottomY + depth);
ctx.lineTo(xs, midY + depth);
ctx.lineTo(xs, midY);
ctx.closePath();
ctx.fill();
// Right Face
ctx.fillStyle = Phaser.Display.Color.IntegerToColor(waterColor).darken(20).rgba;
ctx.beginPath();
ctx.moveTo(xe, midY);
ctx.lineTo(xe, midY + depth);
ctx.lineTo(midX, bottomY + depth);
ctx.lineTo(midX, bottomY);
ctx.closePath();
ctx.fill();
// Top Face
ctx.fillStyle = Phaser.Display.Color.IntegerToColor(waterColor).rgba;
ctx.beginPath();
ctx.moveTo(xs, midY);
ctx.lineTo(midX, topY);
ctx.lineTo(xe, midY);
ctx.lineTo(midX, bottomY);
ctx.closePath();
ctx.fill();
// Shimmer highlights
ctx.fillStyle = `rgba(255, 255, 255, ${0.1 + Math.abs(shimmerOffset) / 100})`;
for (let i = 0; i < 5; i++) {
const sx = xs + 8 + Math.random() * 28;
const sy = topY + 4 + Math.random() * 16;
ctx.fillRect(sx, sy, 3, 2);
}
// Wave lines
ctx.strokeStyle = `rgba(100, 100, 255, ${0.3 + (f / frames) * 0.2})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(xs + 10, midY + 8);
ctx.lineTo(xe - 10, midY - 2);
ctx.stroke();
canvas.refresh();
}
}
constructor(scene) {