570 lines
17 KiB
JavaScript
570 lines
17 KiB
JavaScript
/**
|
|
* ZOMBIE ECONOMY & CITY SANITATION SYSTEM
|
|
* Mrtva Dolina - Worker Zombies, Smetarji, Redka Darila
|
|
*
|
|
* Features:
|
|
* - Worker Zombies (heavy labor, construction)
|
|
* - Sanitation System (Smetar cleans city)
|
|
* - Rare Gift System (unique rewards from zombie work)
|
|
* - Zombie Maintenance (Brain feeding)
|
|
* - Contract System (loan zombies to NPCs)
|
|
* - Happiness impact on city growth
|
|
*/
|
|
|
|
export class ZombieEconomySystem {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
|
|
// Worker zombie types
|
|
this.zombieTypes = {
|
|
scout: {
|
|
name: 'Zombi Skavt',
|
|
strength: 30,
|
|
speed: 60,
|
|
brainConsumption: 0.5, // per hour
|
|
tasks: ['scouting', 'light_work']
|
|
},
|
|
worker: {
|
|
name: 'Delovni Zombi',
|
|
strength: 80,
|
|
speed: 40,
|
|
brainConsumption: 1.5, // per hour
|
|
tasks: ['construction', 'hauling', 'sanitation']
|
|
},
|
|
sanitation: {
|
|
name: 'Smetar Zombi',
|
|
strength: 50,
|
|
speed: 50,
|
|
brainConsumption: 1.0, // per hour
|
|
tasks: ['cleaning', 'graffiti_removal']
|
|
}
|
|
};
|
|
|
|
// Active zombies
|
|
this.activeZombies = [];
|
|
|
|
// Contracts (zombies loaned to NPCs)
|
|
this.activeContracts = [];
|
|
|
|
// Rare gifts catalogue
|
|
this.rareGifts = [
|
|
{ id: 'family_heirloom', name: 'Starinski družinski artefakt', rarity: 'legendary', value: 1000 },
|
|
{ id: 'ancient_dye', name: 'Starodavno barvilo', rarity: 'epic', value: 500 },
|
|
{ id: 'rare_seed_pack', name: 'Paket redkih semen', rarity: 'rare', value: 300 },
|
|
{ id: 'kai_memory_photo', name: 'Kaijeva skrita fotografija', rarity: 'legendary', value: 2000 },
|
|
{ id: 'mystical_paint', name: 'Mistično barvilo', rarity: 'epic', value: 600 }
|
|
];
|
|
|
|
// City cleanliness tracking
|
|
this.cityTrash = [];
|
|
this.cityGraffiti = [];
|
|
this.cleanlinessScore = 50; // 0-100
|
|
|
|
// Happiness impact
|
|
this.citizenHappiness = 50; // 0-100
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Listen for trash generation
|
|
this.scene.events.on('npc:activity', this.onNPCActivity, this);
|
|
this.scene.events.on('vandal:graffiti', this.onGraffitiCreated, this);
|
|
|
|
// Start passive systems
|
|
this.startBrainConsumption();
|
|
this.startCleaningSweep();
|
|
|
|
console.log('✅ ZombieEconomySystem initialized');
|
|
}
|
|
|
|
/**
|
|
* RECRUIT ZOMBIE
|
|
*/
|
|
recruitZombie(type, name = null) {
|
|
const zombieData = this.zombieTypes[type];
|
|
if (!zombieData) {
|
|
console.error(`Unknown zombie type: ${type}`);
|
|
return null;
|
|
}
|
|
|
|
const zombie = {
|
|
id: `zombie_${Date.now()}`,
|
|
type: type,
|
|
name: name || zombieData.name,
|
|
strength: zombieData.strength,
|
|
speed: zombieData.speed,
|
|
brainLevel: 100, // 0-100, energy/hunger
|
|
brainConsumption: zombieData.brainConsumption,
|
|
tasks: zombieData.tasks,
|
|
currentTask: null,
|
|
isContracted: false,
|
|
grumbleTimer: null,
|
|
x: this.scene.player.x,
|
|
y: this.scene.player.y,
|
|
sprite: null
|
|
};
|
|
|
|
// Create sprite
|
|
zombie.sprite = this.scene.add.sprite(zombie.x, zombie.y, `zombie_${type}`);
|
|
zombie.sprite.setDepth(5);
|
|
|
|
this.activeZombies.push(zombie);
|
|
|
|
console.log(`🧟 Recruited: ${zombie.name}`);
|
|
|
|
return zombie;
|
|
}
|
|
|
|
/**
|
|
* FEED ZOMBIE (with brains)
|
|
*/
|
|
feedZombie(zombieId, brainsAmount = 50) {
|
|
const zombie = this.activeZombies.find(z => z.id === zombieId);
|
|
if (!zombie) return false;
|
|
|
|
zombie.brainLevel = Math.min(100, zombie.brainLevel + brainsAmount);
|
|
|
|
// Happy zombie sound
|
|
this.scene.sound.play('zombie_satisfied', { volume: 0.5 });
|
|
|
|
// Show feedback
|
|
this.scene.events.emit('show-floating-text', {
|
|
x: zombie.sprite.x,
|
|
y: zombie.sprite.y - 30,
|
|
text: '*munch* ...dobr!',
|
|
color: '#90EE90'
|
|
});
|
|
|
|
// Stop grumbling
|
|
if (zombie.grumbleTimer) {
|
|
clearInterval(zombie.grumbleTimer);
|
|
zombie.grumbleTimer = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* BRAIN CONSUMPTION - Passive hunger system
|
|
*/
|
|
startBrainConsumption() {
|
|
setInterval(() => {
|
|
this.activeZombies.forEach(zombie => {
|
|
// Reduce brain level based on consumption rate
|
|
zombie.brainLevel = Math.max(0, zombie.brainLevel - (zombie.brainConsumption / 60)); // per minute
|
|
|
|
// Check hunger
|
|
if (zombie.brainLevel < 30) {
|
|
this.zombieHungry(zombie);
|
|
}
|
|
|
|
// Critical hunger - zombie becomes slow
|
|
if (zombie.brainLevel < 10) {
|
|
zombie.speed *= 0.5; // 50% slower
|
|
if (zombie.sprite) {
|
|
zombie.sprite.setTint(0x666666); // Darken sprite
|
|
}
|
|
}
|
|
});
|
|
}, 60000); // Every minute
|
|
}
|
|
|
|
zombieHungry(zombie) {
|
|
if (zombie.grumbleTimer) return; // Already grumbling
|
|
|
|
// Start periodic grumbling
|
|
zombie.grumbleTimer = setInterval(() => {
|
|
const hungerLines = [
|
|
'Braaaaains...',
|
|
'Možgaaaaani...',
|
|
'Hrrrngh... lačen...',
|
|
'*godrnja o hrani*'
|
|
];
|
|
|
|
const line = Phaser.Utils.Array.GetRandom(hungerLines);
|
|
|
|
// Show speech bubble
|
|
this.scene.events.emit('show-speech-bubble', {
|
|
x: zombie.sprite.x,
|
|
y: zombie.sprite.y - 50,
|
|
text: line,
|
|
duration: 3000
|
|
});
|
|
|
|
// Play hungry sound
|
|
if (this.scene.sound) {
|
|
this.scene.sound.play('zombie_groan', { volume: 0.4 });
|
|
}
|
|
}, 15000); // Every 15 seconds
|
|
}
|
|
|
|
/**
|
|
* CONTRACT SYSTEM - Loan zombies to NPCs
|
|
*/
|
|
createContract(zombieId, npc, task, duration, payment) {
|
|
const zombie = this.activeZombies.find(z => z.id === zombieId);
|
|
if (!zombie || zombie.isContracted) {
|
|
console.warn('Zombie not available for contract');
|
|
return false;
|
|
}
|
|
|
|
const contract = {
|
|
id: `contract_${Date.now()}`,
|
|
zombieId: zombieId,
|
|
npc: npc, // 'ivan_kovac', 'tehnik', etc.
|
|
task: task, // 'wall_construction', 'steel_hauling', etc.
|
|
duration: duration, // in game hours
|
|
payment: payment, // gold or rare gift
|
|
startTime: this.scene.gameTime || Date.now(),
|
|
completed: false
|
|
};
|
|
|
|
zombie.isContracted = true;
|
|
zombie.currentTask = task;
|
|
this.activeContracts.push(contract);
|
|
|
|
// Move zombie to work site
|
|
this.moveZombieToWorkSite(zombie, npc);
|
|
|
|
console.log(`📄 Contract created: ${zombie.name} → ${npc} (${task})`);
|
|
|
|
// Start work visuals
|
|
this.startWorkAnimation(zombie, task);
|
|
|
|
return contract;
|
|
}
|
|
|
|
moveZombieToWorkSite(zombie, npc) {
|
|
const workSites = {
|
|
ivan_kovac: { x: 300, y: 200 }, // Blacksmith
|
|
tehnik: { x: 500, y: 250 }, // Tech Workshop
|
|
mayor: { x: 400, y: 300 } // Town Hall
|
|
};
|
|
|
|
const site = workSites[npc] || { x: 400, y: 300 };
|
|
|
|
if (zombie.sprite) {
|
|
this.scene.tweens.add({
|
|
targets: zombie.sprite,
|
|
x: site.x,
|
|
y: site.y,
|
|
duration: 2000,
|
|
ease: 'Sine.easeInOut'
|
|
});
|
|
}
|
|
}
|
|
|
|
startWorkAnimation(zombie, task) {
|
|
// Different animations for different tasks
|
|
const animations = {
|
|
wall_construction: 'zombie_hammering',
|
|
steel_hauling: 'zombie_carrying',
|
|
sanitation: 'zombie_sweeping',
|
|
graffiti_removal: 'zombie_scrubbing'
|
|
};
|
|
|
|
const anim = animations[task] || 'zombie_working';
|
|
|
|
if (zombie.sprite && zombie.sprite.anims) {
|
|
zombie.sprite.play(anim, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* COMPLETE CONTRACT - Award payment
|
|
*/
|
|
completeContract(contractId) {
|
|
const contract = this.activeContracts.find(c => c.id === contractId);
|
|
if (!contract || contract.completed) return;
|
|
|
|
const zombie = this.activeZombies.find(z => z.id === contract.zombieId);
|
|
|
|
contract.completed = true;
|
|
zombie.isContracted = false;
|
|
zombie.currentTask = null;
|
|
|
|
// Award payment
|
|
if (contract.payment.type === 'gold') {
|
|
this.scene.inventorySystem.addGold(contract.payment.amount);
|
|
|
|
this.scene.events.emit('show-notification', {
|
|
title: 'Pogodba Končana',
|
|
message: `${zombie.name} je končal delo! +${contract.payment.amount} zlata.`,
|
|
icon: '💰',
|
|
duration: 3000
|
|
});
|
|
} else if (contract.payment.type === 'rare_gift') {
|
|
this.awardRareGift(contract.payment.giftId);
|
|
}
|
|
|
|
// Return zombie to player
|
|
if (zombie.sprite) {
|
|
this.scene.tweens.add({
|
|
targets: zombie.sprite,
|
|
x: this.scene.player.x + 50,
|
|
y: this.scene.player.y,
|
|
duration: 2000,
|
|
ease: 'Sine.easeOut'
|
|
});
|
|
}
|
|
|
|
console.log(`✅ Contract completed: ${contract.task}`);
|
|
}
|
|
|
|
/**
|
|
* RARE GIFT SYSTEM
|
|
*/
|
|
awardRareGift(giftId = null) {
|
|
// Random gift if not specified
|
|
if (!giftId) {
|
|
const gift = Phaser.Utils.Array.GetRandom(this.rareGifts);
|
|
giftId = gift.id;
|
|
}
|
|
|
|
const gift = this.rareGifts.find(g => g.id === giftId);
|
|
if (!gift) return;
|
|
|
|
// Add to inventory
|
|
if (this.scene.inventorySystem) {
|
|
this.scene.inventorySystem.addItem(gift.id, 1);
|
|
}
|
|
|
|
// Show special notification
|
|
this.scene.events.emit('show-notification', {
|
|
title: '🎁 REDKO DARILO!',
|
|
message: `Prejel si: ${gift.name} (${gift.rarity})`,
|
|
icon: '✨',
|
|
duration: 7000,
|
|
color: this.getRarityColor(gift.rarity)
|
|
});
|
|
|
|
// Play special sound
|
|
if (this.scene.sound) {
|
|
this.scene.sound.play('rare_gift_fanfare', { volume: 0.7 });
|
|
}
|
|
|
|
console.log(`🎁 Awarded rare gift: ${gift.name}`);
|
|
}
|
|
|
|
getRarityColor(rarity) {
|
|
const colors = {
|
|
legendary: '#FFD700', // Gold
|
|
epic: '#9B59B6', // Purple
|
|
rare: '#3498DB' // Blue
|
|
};
|
|
return colors[rarity] || '#FFFFFF';
|
|
}
|
|
|
|
/**
|
|
* SANITATION SYSTEM - Trash & Graffiti
|
|
*/
|
|
onNPCActivity(npcData) {
|
|
// NPCs randomly drop trash
|
|
if (Math.random() < 0.1) { // 10% chance
|
|
this.spawnTrash(npcData.x, npcData.y);
|
|
}
|
|
}
|
|
|
|
spawnTrash(x, y) {
|
|
const trashTypes = ['trash_bag', 'litter', 'broken_bottle', 'paper_waste'];
|
|
const type = Phaser.Utils.Array.GetRandom(trashTypes);
|
|
|
|
const trash = this.scene.add.sprite(x, y, type);
|
|
trash.setDepth(1);
|
|
|
|
this.cityTrash.push({
|
|
id: `trash_${Date.now()}_${Math.random()}`,
|
|
x: x,
|
|
y: y,
|
|
type: type,
|
|
sprite: trash
|
|
});
|
|
|
|
// Lower cleanliness
|
|
this.cleanlinessScore = Math.max(0, this.cleanlinessScore - 2);
|
|
this.updateCityStats();
|
|
}
|
|
|
|
onGraffitiCreated(graffitiData) {
|
|
const graffiti = this.scene.add.sprite(graffitiData.x, graffitiData.y, 'graffiti_tag');
|
|
graffiti.setDepth(2);
|
|
|
|
this.cityGraffiti.push({
|
|
id: `graffiti_${Date.now()}`,
|
|
x: graffitiData.x,
|
|
y: graffitiData.y,
|
|
sprite: graffiti
|
|
});
|
|
|
|
// Lower cleanliness
|
|
this.cleanlinessScore = Math.max(0, this.cleanlinessScore - 5);
|
|
this.updateCityStats();
|
|
}
|
|
|
|
/**
|
|
* CLEANING SWEEP - Zombies clean autonomously
|
|
*/
|
|
startCleaningSweep() {
|
|
setInterval(() => {
|
|
// Find sanitation zombies
|
|
const cleaners = this.activeZombies.filter(z =>
|
|
z.tasks.includes('cleaning') &&
|
|
!z.isContracted &&
|
|
z.brainLevel > 20
|
|
);
|
|
|
|
cleaners.forEach(cleaner => {
|
|
// Clean nearest trash
|
|
const nearestTrash = this.findNearestTrash(cleaner.sprite.x, cleaner.sprite.y);
|
|
if (nearestTrash) {
|
|
this.cleanTrash(cleaner, nearestTrash);
|
|
}
|
|
|
|
// Clean nearest graffiti
|
|
const nearestGraffiti = this.findNearestGraffiti(cleaner.sprite.x, cleaner.sprite.y);
|
|
if (nearestGraffiti) {
|
|
this.cleanGraffiti(cleaner, nearestGraffiti);
|
|
}
|
|
});
|
|
}, 10000); // Every 10 seconds
|
|
}
|
|
|
|
findNearestTrash(x, y) {
|
|
let nearest = null;
|
|
let minDist = Infinity;
|
|
|
|
this.cityTrash.forEach(trash => {
|
|
const dist = Phaser.Math.Distance.Between(x, y, trash.x, trash.y);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
nearest = trash;
|
|
}
|
|
});
|
|
|
|
return minDist < 200 ? nearest : null; // Within 200px
|
|
}
|
|
|
|
findNearestGraffiti(x, y) {
|
|
let nearest = null;
|
|
let minDist = Infinity;
|
|
|
|
this.cityGraffiti.forEach(graffiti => {
|
|
const dist = Phaser.Math.Distance.Between(x, y, graffiti.x, graffiti.y);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
nearest = graffiti;
|
|
}
|
|
});
|
|
|
|
return minDist < 200 ? nearest : null;
|
|
}
|
|
|
|
cleanTrash(zombie, trash) {
|
|
// Move to trash
|
|
this.scene.tweens.add({
|
|
targets: zombie.sprite,
|
|
x: trash.x,
|
|
y: trash.y,
|
|
duration: 1000,
|
|
onComplete: () => {
|
|
// Play cleaning animation
|
|
if (zombie.sprite.anims) {
|
|
zombie.sprite.play('zombie_sweeping', true);
|
|
}
|
|
|
|
// Remove trash after delay
|
|
setTimeout(() => {
|
|
trash.sprite.destroy();
|
|
this.cityTrash = this.cityTrash.filter(t => t.id !== trash.id);
|
|
|
|
// Increase cleanliness
|
|
this.cleanlinessScore = Math.min(100, this.cleanlinessScore + 3);
|
|
this.updateCityStats();
|
|
|
|
console.log(`🧹 ${zombie.name} cleaned trash`);
|
|
}, 2000);
|
|
}
|
|
});
|
|
}
|
|
|
|
cleanGraffiti(zombie, graffiti) {
|
|
// Move to graffiti
|
|
this.scene.tweens.add({
|
|
targets: zombie.sprite,
|
|
x: graffiti.x,
|
|
y: graffiti.y,
|
|
duration: 1000,
|
|
onComplete: () => {
|
|
// Play scrubbing animation
|
|
if (zombie.sprite.anims) {
|
|
zombie.sprite.play('zombie_scrubbing', true);
|
|
}
|
|
|
|
// Remove graffiti with fade
|
|
this.scene.tweens.add({
|
|
targets: graffiti.sprite,
|
|
alpha: 0,
|
|
duration: 3000,
|
|
onComplete: () => {
|
|
graffiti.sprite.destroy();
|
|
this.cityGraffiti = this.cityGraffiti.filter(g => g.id !== graffiti.id);
|
|
|
|
// Increase cleanliness
|
|
this.cleanlinessScore = Math.min(100, this.cleanlinessScore + 5);
|
|
this.updateCityStats();
|
|
|
|
console.log(`🧽 ${zombie.name} removed graffiti`);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* CITY STATS UPDATE
|
|
*/
|
|
updateCityStats() {
|
|
// Calculate happiness based on cleanliness
|
|
this.citizenHappiness = Math.min(100, this.cleanlinessScore * 1.2);
|
|
|
|
// Emit stats update
|
|
this.scene.events.emit('city:stats_updated', {
|
|
cleanliness: this.cleanlinessScore,
|
|
happiness: this.citizenHappiness,
|
|
trashCount: this.cityTrash.length,
|
|
graffitiCount: this.cityGraffiti.length
|
|
});
|
|
|
|
// Happiness affects NPC arrival rate
|
|
if (this.citizenHappiness > 70) {
|
|
// Faster settler arrivals
|
|
this.scene.events.emit('city:boost_immigration');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get zombie status for UI
|
|
*/
|
|
getZombieStatus() {
|
|
return this.activeZombies.map(z => ({
|
|
id: z.id,
|
|
name: z.name,
|
|
type: z.type,
|
|
brainLevel: z.brainLevel,
|
|
currentTask: z.currentTask,
|
|
isContracted: z.isContracted
|
|
}));
|
|
}
|
|
|
|
destroy() {
|
|
this.activeZombies.forEach(zombie => {
|
|
if (zombie.sprite) zombie.sprite.destroy();
|
|
if (zombie.grumbleTimer) clearInterval(zombie.grumbleTimer);
|
|
});
|
|
|
|
this.cityTrash.forEach(trash => trash.sprite.destroy());
|
|
this.cityGraffiti.forEach(graffiti => graffiti.sprite.destroy());
|
|
}
|
|
}
|