/** * 🏙️ CITY MANAGEMENT SYSTEM - Week 1 Priority * Population tracking + Zombie Statistician NPC + City Hall * * Features: * - Population statistics tracking (living, zombies, workers) * - Zombie Statistician NPC employment (1 Cekin/day) * - Daily population board updates * - City Hall building placement * - Worker management integration * * Assets Used: * - /assets/characters/zombie_statistician/ (11 sprites) * - /assets/buildings/city_hall.png * - /assets/buildings/population_board.png * - /assets/ui/currency_cekin.png */ export default class CityManagementSystem { constructor(scene) { this.scene = scene; // Population statistics this.population = { living: 1, // Player starts zombies: 0, workers: 0, // Employed NPCs total: 1 }; // Buildings this.cityHall = null; this.populationBoard = null; // Zombie Statistician NPC this.statistician = null; this.statisticianEmployed = false; this.statisticianSalary = 1; // Cekini per day this.lastUpdateTime = null; // Board update schedule this.updateHour = 9; // Updates at 9 AM daily this.hasUpdatedToday = false; this.init(); } init() { console.log('[CityManagement] Initializing city management system...'); // Load sprites this.loadSprites(); // Setup daily update check this.setupDailyUpdate(); } loadSprites() { // City Hall if (!this.scene.textures.exists('city_hall')) { this.scene.load.image('city_hall', 'assets/buildings/city_hall.png'); } // Population board if (!this.scene.textures.exists('population_board')) { this.scene.load.image('population_board', 'assets/buildings/population_board.png'); } // Zombie Statistician sprites const directions = ['south', 'north', 'east', 'west']; const actions = ['idle', 'walk']; directions.forEach(dir => { actions.forEach(action => { const key = `statistician_${action}_${dir}`; if (!this.scene.textures.exists(key)) { this.scene.load.image(key, `assets/characters/zombie_statistician/${action}_${dir}.png`); } }); }); // Action sprites if (!this.scene.textures.exists('statistician_action_update')) { this.scene.load.image('statistician_action_update', 'assets/characters/zombie_statistician/action_update.png'); } if (!this.scene.textures.exists('statistician_action_calculate')) { this.scene.load.image('statistician_action_calculate', 'assets/characters/zombie_statistician/action_calculate.png'); } if (!this.scene.textures.exists('statistician_portrait')) { this.scene.load.image('statistician_portrait', 'assets/characters/zombie_statistician/portrait.png'); } // Currency icon if (!this.scene.textures.exists('currency_cekin')) { this.scene.load.image('currency_cekin', 'assets/ui/currency_cekin.png'); } this.scene.load.start(); } setupDailyUpdate() { // Check for daily update every hour this.scene.time.addEvent({ delay: 60000, // Check every minute (in-game might be faster) callback: () => this.checkDailyUpdate(), loop: true }); } /** * Place City Hall building */ placeCityHall(x, y) { if (this.cityHall) { console.warn('[CityManagement] City Hall already exists!'); return; } this.cityHall = this.scene.add.sprite(x, y, 'city_hall'); this.cityHall.setOrigin(0.5, 0.5); this.cityHall.setInteractive(); // Click to open city management UI this.cityHall.on('pointerdown', () => { this.openCityManagementUI(); }); console.log('[CityManagement] City Hall placed at', x, y); } /** * Place Population Board */ placePopulationBoard(x, y) { if (this.populationBoard) { console.warn('[CityManagement] Population Board already exists!'); return; } this.populationBoard = this.scene.add.sprite(x, y, 'population_board'); this.populationBoard.setOrigin(0.5, 0.5); this.populationBoard.setInteractive(); // Create text overlay for statistics this.createBoardText(x, y); // Click to view detailed stats this.populationBoard.on('pointerdown', () => { this.showDetailedStats(); }); console.log('[CityManagement] Population Board placed at', x, y); } /** * Create text overlay on population board */ createBoardText(x, y) { const textStyle = { fontSize: '14px', fill: '#ffffff', stroke: '#000000', strokeThickness: 2, align: 'left' }; this.boardText = this.scene.add.text(x - 30, y - 20, '', textStyle); this.updateBoardText(); } /** * Update board text with current stats */ updateBoardText() { if (!this.boardText) return; const text = `Population: ${this.population.living}\nZombies: ${this.population.zombies}\nWorkers: ${this.population.workers}`; this.boardText.setText(text); } /** * Spawn Zombie Statistician NPC */ spawnStatistician(x, y) { if (this.statistician) { console.warn('[CityManagement] Statistician already exists!'); return; } this.statistician = this.scene.add.sprite(x, y, 'statistician_idle_south'); this.statistician.setOrigin(0.5, 0.5); this.statistician.setInteractive(); // NPC data this.statistician.npcData = { name: 'Zombie Statistician', type: 'office_zombie', employed: false, salary: this.statisticianSalary, workLocation: this.populationBoard, isZombie: true }; // Click to hire/talk this.statistician.on('pointerdown', () => { this.interactWithStatistician(); }); // Basic idle animation this.createStatisticianIdleAnimation(); console.log('[CityManagement] Zombie Statistician spawned at', x, y); } /** * Create idle animation for Statistician */ createStatisticianIdleAnimation() { if (!this.statistician) return; this.scene.time.addEvent({ delay: 2000, callback: () => { if (this.statistician && !this.statistician.isWalking) { // Randomly look around (change direction) const directions = ['south', 'north', 'east', 'west']; const randomDir = Phaser.Utils.Array.GetRandom(directions); this.statistician.setTexture(`statistician_idle_${randomDir}`); } }, loop: true }); } /** * Interact with Statistician (hire or talk) */ interactWithStatistician() { if (!this.statistician) return; if (!this.statisticianEmployed) { // Show employment dialog this.showEmploymentDialog(); } else { // Show stats dialog this.showStatisticianDialog(); } } /** * Show employment dialog */ showEmploymentDialog() { const dialogText = `Would you like to employ me?\n\nSalary: ${this.statisticianSalary} Cekin/day\n\nI will update the population board daily at ${this.updateHour}:00 AM with accurate statistics.`; if (this.scene.dialogueSystem) { this.scene.dialogueSystem.showDialog({ portrait: 'statistician_portrait', name: 'Zombie Statistician', text: dialogText, choices: [ { text: `Hire (${this.statisticianSalary} Cekin/day)`, callback: () => this.hireStatistician() }, { text: 'Not now', callback: () => console.log('[CityManagement] Employment declined') } ] }); } else { console.log('[CityManagement] Employment dialog:', dialogText); // Auto-hire for now this.hireStatistician(); } } /** * Hire the Statistician */ hireStatistician() { // Check if player has enough money (if economy system exists) if (this.scene.economySystem) { if (!this.scene.economySystem.hasEnoughCekini(this.statisticianSalary)) { console.warn('[CityManagement] Not enough Cekini to hire Statistician!'); return; this.statisticianEmployed = true; this.statistician.npcData.employed = true; this.population.workers++; // Add to worker count this.updatePopulation(); console.log('[CityManagement] Zombie Statistician hired! Salary:', this.statisticianSalary, 'Cekini/day'); // Start daily work routine this.startStatisticianWorkRoutine(); } /** * Start Statistician's daily work routine */ startStatisticianWorkRoutine() { if (!this.statisticianEmployed) return; // Move to board at update time this.scene.time.addEvent({ delay: 60000, // Check every minute callback: () => { // Check if it's update time if (this.scene.timeSystem) { const hour = this.scene.timeSystem.getCurrentHour(); if (hour === this.updateHour && !this.hasUpdatedToday) { this.statisticianUpdateBoard(); } } }, loop: true }); } /** * Statistician updates the board (animation + action) */ statisticianUpdateBoard() { if (!this.statistician || !this.populationBoard) return; console.log('[CityManagement] Statistician updating population board...'); // Walk to board const targetX = this.populationBoard.x; const targetY = this.populationBoard.y + 50; // Stand in front // Simple tween movement this.scene.tweens.add({ targets: this.statistician, x: targetX, y: targetY, duration: 2000, ease: 'Linear', onUpdate: () => { // Use walk animation this.statistician.setTexture('statistician_walk_south'); }, onComplete: () => { // Show update action this.statistician.setTexture('statistician_action_update'); // Update the board after 2 seconds this.scene.time.delayedCall(2000, () => { this.updateBoardText(); this.hasUpdatedToday = true; // Return to idle this.statistician.setTexture('statistician_idle_south'); console.log('[CityManagement] Board updated!'); }); } }); } /** * Check if daily update should happen */ checkDailyUpdate() { // Reset flag at midnight if (this.scene.timeSystem) { const hour = this.scene.timeSystem.getCurrentHour(); if (hour === 0 && this.hasUpdatedToday) { this.hasUpdatedToday = false; } } } /** * Update population stats */ updatePopulation(living = null, zombies = null, workers = null) { if (living !== null) this.population.living = living; if (zombies !== null) this.population.zombies = zombies; if (workers !== null) this.population.workers = workers; this.population.total = this.population.living + this.population.zombies; this.updateBoardText(); console.log('[CityManagement] Population updated:', this.population); } /** * Add population (new settler, zombie conversion, etc.) */ addPopulation(type, count = 1) { if (type === 'living') { this.population.living += count; } else if (type === 'zombie') { this.population.zombies += count; } else if (type === 'worker') { this.population.workers += count; } this.updatePopulation(); } /** * Remove population (death, leaving, etc.) */ removePopulation(type, count = 1) { if (type === 'living') { this.population.living = Math.max(0, this.population.living - count); } else if (type === 'zombie') { this.population.zombies = Math.max(0, this.population.zombies - count); } else if (type === 'worker') { this.population.workers = Math.max(0, this.population.workers - count); } this.updatePopulation(); } /** * Show detailed statistics in UI */ showDetailedStats() { const stats = ` === CITY STATISTICS === Living Population: ${this.population.living} Zombie Population: ${this.population.zombies} Employed Workers: ${this.population.workers} Total Population: ${this.population.total} Updated by: ${this.statisticianEmployed ? 'Zombie Statistician' : 'Manual'} Last Update: ${this.hasUpdatedToday ? 'Today' : 'Not today'} `; console.log(stats); if (this.scene.centralPopupSystem) { this.scene.centralPopupSystem.showMessage(stats, 'info'); } } /** * Open City Management UI */ openCityManagementUI() { console.log('[CityManagement] Opening city management UI...'); // TODO: Create full city management UI panel // For now, show stats this.showDetailedStats(); } /** * Show Statistician dialogue */ showStatisticianDialog() { const dialogues = [ "Population count: accurate as always.", "The numbers... they must be precise.", "32 zombies counted this morning.", "Statistics show a 2% increase in workers.", "Even in undeath, I serve the data.", "The board is updated daily at 9 AM sharp." ]; const randomDialogue = Phaser.Utils.Array.GetRandom(dialogues); if (this.scene.dialogueSystem) { this.scene.dialogueSystem.showDialog({ portrait: 'statistician_portrait', name: 'Zombie Statistician', text: randomDialogue }); } else { console.log('[CityManagement] Statistician:', randomDialogue); } } /** * Get current population stats */ getPopulation() { return { ...this.population }; } /** * Pay daily salaries */ payDailySalaries() { if (!this.statisticianEmployed) return; if (this.scene.economySystem) { const paid = this.scene.economySystem.spendCekini(this.statisticianSalary); if (paid) { console.log(`[CityManagement] Paid Statistician ${this.statisticianSalary} Cekini`); } else { console.warn('[CityManagement] Not enough Cekini to pay Statistician!'); // Could fire worker or create debt } } } update(time, delta) { // System updates handled by time events } destroy() { if (this.cityHall) this.cityHall.destroy(); if (this.populationBoard) this.populationBoard.destroy(); if (this.boardText) this.boardText.destroy(); if (this.statistician) this.statistician.destroy(); } }