ok
This commit is contained in:
516
EMERGENCY_SYSTEMS_RECOVERY/CityManagementSystem.js
Normal file
516
EMERGENCY_SYSTEMS_RECOVERY/CityManagementSystem.js
Normal file
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* 🏙️ 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user