diff --git a/src/systems/BuildingUpgradeSystem.js b/src/systems/BuildingUpgradeSystem.js new file mode 100644 index 000000000..1f43ff0ad --- /dev/null +++ b/src/systems/BuildingUpgradeSystem.js @@ -0,0 +1,680 @@ +/** + * ⚡ BUILDING UPGRADE SYSTEM - Week 1 Priority + * Generator + Power Grid + Electrician NPC + Maintenance + * + * Features: + * - Generator building (electricity for city) + * - Power grid system (poles connecting generator) + * - Electrician NPC employment (2 Cekini/day) + * - Repair mechanics (generator, poles, UV lights) + * - Breakdown prevention when Electrician employed + * - Electric sparks VFX + Repair sparkles + * + * Assets Used: + * - /assets/buildings/generator.png + * - /assets/buildings/power_pole_straight.png + * - /assets/buildings/power_pole_corner.png + * - /assets/characters/electrician/ (11 sprites) + * - /assets/vfx/electric_spark.png + * - /assets/vfx/sparkle_repair.png + */ + +export default class BuildingUpgradeSystem { + constructor(scene) { + this.scene = scene; + + // Generator system + this.generator = null; + this.generatorHealth = 100; + this.generatorMaxHealth = 100; + this.generatorActive = false; + this.generatorBreakdownChance = 0.05; // 5% per day without Electrician + + // Power grid + this.powerPoles = []; + this.isPowered = false; + + // Electrician NPC + this.electrician = null; + this.electricianEmployed = false; + this.electricianSalary = 2; // Cekini per day + this.electricianWorkSchedule = { + inspectionTime: 10, // 10 AM + repairTime: 14 // 2 PM + }; + + // Repair system + this.repairCost = { + generator: 50, // Wood + powerPole: 20, + uvLight: 30 + }; + + // Buildings that need electricity + this.electricBuildings = []; + + this.init(); + } + + init() { + console.log('[BuildingUpgrade] Initializing building upgrade system...'); + + // Load sprites + this.loadSprites(); + + // Setup daily maintenance check + this.setupMaintenanceRoutine(); + } + + loadSprites() { + // Generator + if (!this.scene.textures.exists('generator')) { + this.scene.load.image('generator', 'assets/buildings/generator.png'); + } + + // Power poles + if (!this.scene.textures.exists('power_pole_straight')) { + this.scene.load.image('power_pole_straight', 'assets/buildings/power_pole_straight.png'); + } + if (!this.scene.textures.exists('power_pole_corner')) { + this.scene.load.image('power_pole_corner', 'assets/buildings/power_pole_corner.png'); + } + + // Electrician sprites + const directions = ['south', 'north', 'east', 'west']; + const actions = ['idle', 'walk']; + + directions.forEach(dir => { + actions.forEach(action => { + const key = `electrician_${action}_${dir}`; + if (!this.scene.textures.exists(key)) { + this.scene.load.image(key, `assets/characters/electrician/${action}_${dir}.png`); + } + }); + }); + + // Action sprites + if (!this.scene.textures.exists('electrician_action_repair')) { + this.scene.load.image('electrician_action_repair', 'assets/characters/electrician/action_repair.png'); + } + if (!this.scene.textures.exists('electrician_action_inspect')) { + this.scene.load.image('electrician_action_inspect', 'assets/characters/electrician/action_inspect.png'); + } + if (!this.scene.textures.exists('electrician_portrait')) { + this.scene.load.image('electrician_portrait', 'assets/characters/electrician/portrait.png'); + } + + // VFX + if (!this.scene.textures.exists('electric_spark')) { + this.scene.load.image('electric_spark', 'assets/vfx/electric_spark.png'); + } + if (!this.scene.textures.exists('sparkle_repair')) { + this.scene.load.image('sparkle_repair', 'assets/vfx/sparkle_repair.png'); + } + + this.scene.load.start(); + } + + /** + * Build Generator + */ + buildGenerator(x, y) { + if (this.generator) { + console.warn('[BuildingUpgrade] Generator already exists!'); + return false; + } + + // Check resources (if resource system exists) + const cost = { wood: 100, stone: 50, coal: 20 }; + if (this.scene.resourceLogisticsSystem) { + if (!this.scene.resourceLogisticsSystem.hasResources(cost)) { + console.warn('[BuildingUpgrade] Not enough resources to build generator!'); + return false; + } + + // Spend resources + Object.entries(cost).forEach(([type, amount]) => { + this.scene.resourceLogisticsSystem.removeResource(type, amount); + }); + } + + // Create generator sprite + this.generator = this.scene.add.sprite(x, y, 'generator'); + this.generator.setOrigin(0.5, 0.5); + this.generator.setInteractive(); + + // Generator data + this.generator.buildingData = { + type: 'generator', + health: this.generatorMaxHealth, + active: true, + lastMaintenance: Date.now() + }; + + // Click to interact + this.generator.on('pointerdown', () => { + this.interactWithGenerator(); + }); + + // Activate generator + this.generatorActive = true; + this.isPowered = true; + + // Create smoke effect + this.createGeneratorSmoke(x, y); + + console.log('[BuildingUpgrade] Generator built at', x, y); + return true; + } + + /** + * Create smoke effect for generator + */ + createGeneratorSmoke(x, y) { + // Simple smoke particles + const smoke = this.scene.add.particles(x, y - 40, 'electric_spark', { + speed: { min: 20, max: 40 }, + angle: { min: 260, max: 280 }, + scale: { start: 0.3, end: 0 }, + alpha: { start: 0.3, end: 0 }, + lifespan: 2000, + frequency: 500, + tint: 0x666666, + blendMode: 'NORMAL' + }); + + this.generator.smokeEffect = smoke; + } + + /** + * Place power pole + */ + placePowerPole(x, y, type = 'straight') { + const sprite = type === 'straight' ? 'power_pole_straight' : 'power_pole_corner'; + + const pole = this.scene.add.sprite(x, y, sprite); + pole.setOrigin(0.5, 1); // Bottom-centered + pole.setInteractive(); + + pole.poleData = { + type: type, + health: 100, + connected: false + }; + + this.powerPoles.push(pole); + + // Check connections + this.updatePowerGrid(); + + console.log('[BuildingUpgrade] Power pole placed at', x, y); + return pole; + } + + /** + * Update power grid connections + */ + updatePowerGrid() { + if (!this.generator) { + this.isPowered = false; + return; + } + + // Simple proximity check for now + // TODO: Implement proper grid pathfinding + this.powerPoles.forEach(pole => { + const distance = Phaser.Math.Distance.Between( + this.generator.x, this.generator.y, + pole.x, pole.y + ); + + pole.poleData.connected = distance < 500; // 500px max distance + }); + + this.isPowered = this.generatorActive; + } + + /** + * Spawn Electrician NPC + */ + spawnElectrician(x, y) { + if (this.electrician) { + console.warn('[BuildingUpgrade] Electrician already exists!'); + return; + } + + this.electrician = this.scene.add.sprite(x, y, 'electrician_idle_south'); + this.electrician.setOrigin(0.5, 0.5); + this.electrician.setInteractive(); + + // NPC data + this.electrician.npcData = { + name: 'Electrician', + type: 'town_worker', + employed: false, + salary: this.electricianSalary, + workLocation: this.generator, + skills: ['generator_repair', 'power_grid', 'uv_lights'] + }; + + // Click to hire/talk + this.electrician.on('pointerdown', () => { + this.interactWithElectrician(); + }); + + // Idle animation + this.createElectricianIdleAnimation(); + + console.log('[BuildingUpgrade] Electrician spawned at', x, y); + } + + /** + * Create idle animation for Electrician + */ + createElectricianIdleAnimation() { + if (!this.electrician) return; + + this.scene.time.addEvent({ + delay: 3000, + callback: () => { + if (this.electrician && !this.electrician.isWorking) { + // Look around with tools + const directions = ['south', 'east', 'west']; + const randomDir = Phaser.Utils.Array.GetRandom(directions); + this.electrician.setTexture(`electrician_idle_${randomDir}`); + } + }, + loop: true + }); + } + + /** + * Interact with Electrician (hire or request repair) + */ + interactWithElectrician() { + if (!this.electrician) return; + + if (!this.electricianEmployed) { + this.showElectricianEmploymentDialog(); + } else { + this.showElectricianDialog(); + } + } + + /** + * Show employment dialog + */ + showElectricianEmploymentDialog() { + const dialogText = `Need an electrician?\n\nSalary: ${this.electricianSalary} Cekini/day\n\nI'll maintain your generator, prevent breakdowns, and repair everything for FREE when employed!`; + + if (this.scene.dialogueSystem) { + this.scene.dialogueSystem.showDialog({ + portrait: 'electrician_portrait', + name: 'Electrician', + text: dialogText, + choices: [ + { + text: `Hire (${this.electricianSalary} Cekini/day)`, + callback: () => this.hireElectrician() + }, + { + text: 'Not now', + callback: () => console.log('[BuildingUpgrade] Employment declined') + } + ] + }); + } else { + console.log('[BuildingUpgrade] Employment dialog:', dialogText); + // Auto-hire for testing + this.hireElectrician(); + } + } + + /** + * Hire Electrician + */ + hireElectrician() { + this.electricianEmployed = true; + this.electrician.npcData.employed = true; + + // Add to worker count + if (this.scene.cityManagementSystem) { + this.scene.cityManagementSystem.addPopulation('worker', 1); + } + + console.log('[BuildingUpgrade] Electrician hired! Salary:', this.electricianSalary, 'Cekini/day'); + + // Start work routine + this.startElectricianWorkRoutine(); + } + + /** + * Start Electrician's work routine + */ + startElectricianWorkRoutine() { + if (!this.electricianEmployed) return; + + // Daily inspection at 10 AM + this.scene.time.addEvent({ + delay: 60000, // Check every minute + callback: () => { + if (this.scene.timeSystem) { + const hour = this.scene.timeSystem.getCurrentHour(); + + // Morning inspection + if (hour === this.electricianWorkSchedule.inspectionTime) { + this.electricianInspectGenerator(); + } + + // Afternoon repair if needed + if (hour === this.electricianWorkSchedule.repairTime) { + if (this.generatorHealth < this.generatorMaxHealth) { + this.electricianRepairGenerator(); + } + } + } + }, + loop: true + }); + } + + /** + * Electrician inspects generator + */ + electricianInspectGenerator() { + if (!this.electrician || !this.generator) return; + + console.log('[BuildingUpgrade] Electrician inspecting generator...'); + + // Walk to generator + this.walkElectricianTo(this.generator.x, this.generator.y + 60, () => { + // Show inspect action + this.electrician.setTexture('electrician_action_inspect'); + this.electrician.isWorking = true; + + // Check health + this.scene.time.delayedCall(2000, () => { + console.log(`[BuildingUpgrade] Generator health: ${this.generatorHealth}%`); + + // Return to idle + this.electrician.setTexture('electrician_idle_south'); + this.electrician.isWorking = false; + }); + }); + } + + /** + * Electrician repairs generator + */ + electricianRepairGenerator() { + if (!this.electrician || !this.generator) return; + + console.log('[BuildingUpgrade] Electrician repairing generator...'); + + // Walk to generator + this.walkElectricianTo(this.generator.x, this.generator.y + 60, () => { + // Show repair action + this.electrician.setTexture('electrician_action_repair'); + this.electrician.isWorking = true; + + // Play electric sparks VFX + this.playElectricSparks(this.generator.x, this.generator.y); + + // Repair after 3 seconds + this.scene.time.delayedCall(3000, () => { + // Restore health + this.generatorHealth = this.generatorMaxHealth; + if (this.generator.buildingData) { + this.generator.buildingData.health = this.generatorMaxHealth; + } + + // Play repair sparkles + this.playRepairSparkles(this.generator.x, this.generator.y); + + console.log('[BuildingUpgrade] Generator fully repaired!'); + + // Return to idle + this.electrician.setTexture('electrician_idle_south'); + this.electrician.isWorking = false; + }); + }); + } + + /** + * Walk Electrician to location + */ + walkElectricianTo(targetX, targetY, onComplete) { + if (!this.electrician) return; + + // Calculate direction + const dx = targetX - this.electrician.x; + const direction = dx > 0 ? 'east' : 'west'; + + // Walk animation + this.scene.tweens.add({ + targets: this.electrician, + x: targetX, + y: targetY, + duration: 2000, + ease: 'Linear', + onUpdate: () => { + this.electrician.setTexture(`electrician_walk_${direction}`); + }, + onComplete: () => { + if (onComplete) onComplete(); + } + }); + } + + /** + * Play electric sparks VFX + */ + playElectricSparks(x, y) { + if (!this.scene.textures.exists('electric_spark')) return; + + const emitter = this.scene.add.particles(x, y, 'electric_spark', { + speed: { min: 100, max: 200 }, + angle: { min: 0, max: 360 }, + scale: { start: 0.8, end: 0.2 }, + alpha: { start: 1, end: 0 }, + lifespan: 400, + quantity: 3, + frequency: 100, + blendMode: 'ADD', + tint: 0x00FFFF // Blue electric + }); + + // Stop after 3 seconds + this.scene.time.delayedCall(3000, () => { + emitter.stop(); + this.scene.time.delayedCall(500, () => emitter.destroy()); + }); + } + + /** + * Play repair sparkles VFX (4-frame animation!) + */ + playRepairSparkles(x, y) { + if (!this.scene.textures.exists('sparkle_repair')) return; + + const emitter = this.scene.add.particles(x, y - 20, 'sparkle_repair', { + speed: { min: 20, max: 50 }, + angle: { min: 0, max: 360 }, + scale: { start: 1, end: 0 }, + alpha: { start: 1, end: 0 }, + lifespan: 1000, + quantity: 12, + blendMode: 'ADD', + tint: 0xFFD700 // Gold sparkles + }); + + // Destroy after animation + this.scene.time.delayedCall(1500, () => { + emitter.destroy(); + }); + } + + /** + * Daily maintenance check + */ + setupMaintenanceRoutine() { + this.scene.time.addEvent({ + delay: 86400000, // Daily (or faster in-game) + callback: () => this.performDailyMaintenance(), + loop: true + }); + } + + /** + * Perform daily maintenance + */ + performDailyMaintenance() { + if (!this.generator || !this.generatorActive) return; + + if (!this.electricianEmployed) { + // Chance of breakdown without Electrician + if (Math.random() < this.generatorBreakdownChance) { + this.generatorBreakdown(); + } else { + // Gradual degradation + this.generatorHealth -= 10; + if (this.generatorHealth < 0) { + this.generatorBreakdown(); + } + } + } else { + // Electrician prevents breakdowns! + console.log('[BuildingUpgrade] Electrician prevented breakdown!'); + } + } + + /** + * Generator breakdown + */ + generatorBreakdown() { + console.warn('[BuildingUpgrade] GENERATOR BREAKDOWN!'); + + this.generatorActive = false; + this.generatorHealth = 0; + this.isPowered = false; + + // Visual feedback + if (this.generator && this.generator.smokeEffect) { + this.generator.smokeEffect.stop(); + } + + // Show warning + if (this.scene.centralPopupSystem) { + this.scene.centralPopupSystem.showMessage( + 'GENERATOR BREAKDOWN! Hire an Electrician or repair manually!', + 'critical' + ); + } + + // Turn off electric buildings + this.powerOffBuildings(); + } + + /** + * Power off all electric buildings + */ + powerOffBuildings() { + this.electricBuildings.forEach(building => { + if (building.powerOff) { + building.powerOff(); + } + }); + } + + /** + * Interact with generator + */ + interactWithGenerator() { + if (!this.generator) return; + + const health = this.generatorHealth; + const status = this.generatorActive ? 'ACTIVE' : 'OFFLINE'; + + const info = ` +=== GENERATOR STATUS === +Status: ${status} +Health: ${health}/${this.generatorMaxHealth} +Power: ${this.isPowered ? 'ONLINE' : 'OFFLINE'} +Maintenance: ${this.electricianEmployed ? 'Automatic' : 'Manual'} + `; + + console.log(info); + + if (this.scene.centralPopupSystem) { + this.scene.centralPopupSystem.showMessage(info, 'info'); + } + } + + /** + * Show Electrician dialogue + */ + showElectricianDialog() { + const dialogues = [ + "Generator's running smooth. No problems here.", + "Just did my daily inspection. All systems nominal.", + "Power grid is stable. Good work building it.", + "I'll keep everything running. Don't worry.", + "Found a loose wire this morning. Fixed it.", + "UV lights in the basement need checking soon." + ]; + + const randomDialogue = Phaser.Utils.Array.GetRandom(dialogues); + + if (this.scene.dialogueSystem) { + this.scene.dialogueSystem.showDialog({ + portrait: 'electrician_portrait', + name: 'Electrician', + text: randomDialogue + }); + } else { + console.log('[BuildingUpgrade] Electrician:', randomDialogue); + } + } + + /** + * Register electric building + */ + registerElectricBuilding(building) { + this.electricBuildings.push(building); + } + + /** + * Check if power is available + */ + hasPower() { + return this.isPowered; + } + + /** + * Pay daily salaries + */ + payDailySalaries() { + if (!this.electricianEmployed) return; + + if (this.scene.economySystem) { + this.scene.economySystem.spendCekini(this.electricianSalary); + console.log(`[BuildingUpgrade] Paid Electrician ${this.electricianSalary} Cekini`); + } + } + + update(time, delta) { + // System updates handled by time events + } + + destroy() { + if (this.generator) { + if (this.generator.smokeEffect) this.generator.smokeEffect.destroy(); + this.generator.destroy(); + } + + this.powerPoles.forEach(pole => pole.destroy()); + this.powerPoles = []; + + if (this.electrician) this.electrician.destroy(); + } +}