This commit is contained in:
2026-01-16 02:43:46 +01:00
parent bc2225ad64
commit 3ae8d39f9c
218 changed files with 87850 additions and 353 deletions

View File

@@ -0,0 +1,516 @@
/**
* DialogueSystem.js
* =================
* KRVAVA ŽETEV - NPC Dialogue & Conversation System
*
* Features:
* - Dynamic dialogue trees with choices
* - Character portraits and emotions
* - Quest integration (dialogue can trigger/complete quests)
* - Relationship tracking (affects dialogue options)
* - Twin Bond special dialogues (Ana's voice)
* - Memory system (NPCs remember past conversations)
*
* @author NovaFarma Team
* @date 2025-12-23
*/
class DialogueSystem {
constructor(scene) {
this.scene = scene;
// Active dialogue state
this.currentDialogue = null;
this.currentSpeaker = null;
this.currentNode = null;
this.dialogueHistory = new Map(); // NPC ID -> conversation history
// UI elements
this.dialogueBox = null;
this.portraitSprite = null;
this.nameText = null;
this.dialogueText = null;
this.choicesContainer = null;
// Dialogue database
this.dialogues = new Map(); // Dialogue ID -> dialogue data
// Callbacks
this.onDialogueComplete = null;
this.onChoiceMade = null;
console.log('💬 DialogueSystem initialized');
}
/**
* Register a dialogue tree
* @param {string} dialogueId - Unique identifier
* @param {object} dialogueData - Tree structure
*/
registerDialogue(dialogueId, dialogueData) {
this.dialogues.set(dialogueId, dialogueData);
console.log(`💬 Registered dialogue: ${dialogueId}`);
}
/**
* Start a dialogue with an NPC
* @param {string} dialogueId - Which dialogue to start
* @param {object} speaker - NPC or character data
* @param {function} onComplete - Callback when done
*/
startDialogue(dialogueId, speaker, onComplete) {
const dialogueData = this.dialogues.get(dialogueId);
if (!dialogueData) {
console.error(`Dialogue not found: ${dialogueId}`);
return;
}
this.currentDialogue = dialogueData;
this.currentSpeaker = speaker;
this.onDialogueComplete = onComplete;
// Show UI
this.createDialogueUI();
// Start at root node
this.showNode(dialogueData.root || 'start');
// Pause game
this.scene.physics.pause();
console.log(`💬 Started dialogue: ${dialogueId} with ${speaker.name}`);
}
/**
* Show a specific dialogue node
* @param {string} nodeId - Node to display
*/
showNode(nodeId) {
const node = this.currentDialogue.nodes[nodeId];
if (!node) {
console.error(`Node not found: ${nodeId}`);
this.endDialogue();
return;
}
this.currentNode = node;
// Update speaker info
const speaker = node.speaker || this.currentSpeaker.name;
const emotion = node.emotion || 'neutral';
this.updateSpeaker(speaker, emotion);
// Show text with typewriter effect
this.typewriterText(node.text);
// Show choices or continue button
if (node.choices && node.choices.length > 0) {
this.showChoices(node.choices);
} else if (node.next) {
this.showContinueButton(node.next);
} else {
// End of dialogue
this.showContinueButton('END');
}
// Execute node actions
if (node.action) {
this.executeNodeAction(node.action);
}
// Record in history
this.addToHistory(nodeId, node.text);
}
/**
* Create dialogue UI
*/
createDialogueUI() {
const width = this.scene.cameras.main.width;
const height = this.scene.cameras.main.height;
// Container for everything
this.dialogueContainer = this.scene.add.container(0, 0);
this.dialogueContainer.setDepth(1000);
// Dark overlay
const overlay = this.scene.add.rectangle(0, 0, width, height, 0x000000, 0.5);
overlay.setOrigin(0);
overlay.setInteractive();
this.dialogueContainer.add(overlay);
// Dialogue box
const boxY = height - 200;
const boxHeight = 180;
this.dialogueBox = this.scene.add.rectangle(
width / 2, boxY,
width - 80, boxHeight,
0x2d1b00, 0.95
);
this.dialogueBox.setStrokeStyle(3, 0xd4a574);
this.dialogueContainer.add(this.dialogueBox);
// Portrait background
this.portraitBg = this.scene.add.rectangle(100, boxY, 100, 100, 0x4a3520, 0.9);
this.portraitBg.setStrokeStyle(2, 0xd4a574);
this.dialogueContainer.add(this.portraitBg);
// Portrait
this.portraitSprite = this.scene.add.text(100, boxY, '👤', {
fontSize: '64px'
});
this.portraitSprite.setOrigin(0.5);
this.dialogueContainer.add(this.portraitSprite);
// Speaker name
this.nameText = this.scene.add.text(170, boxY - 70, '', {
fontSize: '20px',
fontFamily: 'Georgia, serif',
color: '#FFD700',
fontStyle: 'bold',
stroke: '#000000',
strokeThickness: 3
});
this.dialogueContainer.add(this.nameText);
// Dialogue text
this.dialogueText = this.scene.add.text(170, boxY - 40, '', {
fontSize: '18px',
fontFamily: 'Georgia, serif',
color: '#f4e4c1',
wordWrap: { width: width - 280 },
lineSpacing: 6
});
this.dialogueContainer.add(this.dialogueText);
// Choices container
this.choicesContainer = this.scene.add.container(width / 2, boxY + 120);
this.dialogueContainer.add(this.choicesContainer);
}
/**
* Update speaker display
*/
updateSpeaker(name, emotion) {
this.nameText.setText(name);
// Get portrait based on speaker and emotion
const portrait = this.getPortrait(name, emotion);
this.portraitSprite.setText(portrait);
}
/**
* Get portrait emoji
*/
getPortrait(speaker, emotion) {
// Kai portraits
if (speaker === 'Kai' || speaker === 'You') {
const kaiEmotions = {
'neutral': '👨',
'happy': '😊',
'sad': '😢',
'angry': '😠',
'worried': '😟',
'shocked': '😱',
'determined': '😤'
};
return kaiEmotions[emotion] || '👨';
}
// Ana portraits (Twin Bond - ghostly)
if (speaker === 'Ana' || speaker === 'Ana (Twin Bond)') {
const anaEmotions = {
'neutral': '👻',
'happy': '😇',
'sad': '😰',
'worried': '😨',
'pain': '😣',
'help': '🆘'
};
return anaEmotions[emotion] || '👻';
}
// Other NPCs
const npcPortraits = {
'Grok': '🧘',
'Elder': '👴',
'Blacksmith': '🔨',
'Baker': '🍞',
'Doctor': '⚕️',
'Merchant': '💰',
'Stranger': '❓',
'Zombie': '🧟'
};
return npcPortraits[speaker] || '👤';
}
/**
* Typewriter text effect
*/
typewriterText(text) {
let displayText = '';
let charIndex = 0;
this.dialogueText.setText('');
// Store reference to timer so we can clear it if needed
this.typewriterTimer = this.scene.time.addEvent({
delay: 30,
callback: () => {
// SAFETY CHECK: Ensure text object still exists
if (!this.dialogueText || !this.dialogueText.active) {
if (this.typewriterTimer) this.typewriterTimer.remove();
return;
}
if (charIndex < text.length) {
displayText += text[charIndex];
this.dialogueText.setText(displayText);
charIndex++;
} else {
if (this.typewriterTimer) this.typewriterTimer.remove();
}
},
loop: true
});
}
/**
* Show dialogue choices
*/
showChoices(choices) {
this.choicesContainer.removeAll(true);
choices.forEach((choice, index) => {
// Check if choice is available
if (choice.condition && !this.checkCondition(choice.condition)) {
return; // Skip this choice
}
const y = index * 50;
// Choice button background
const btn = this.scene.add.rectangle(0, y, 600, 40, 0x6b4423, 1);
btn.setStrokeStyle(2, 0xd4a574);
btn.setInteractive({ useHandCursor: true });
// Choice text
const text = this.scene.add.text(0, y, choice.text, {
fontSize: '16px',
fontFamily: 'Georgia, serif',
color: '#f4e4c1',
fontStyle: 'bold'
});
text.setOrigin(0.5);
// Hover effects
btn.on('pointerover', () => {
btn.setFillStyle(0x8b5a3c);
text.setColor('#FFD700');
});
btn.on('pointerout', () => {
btn.setFillStyle(0x6b4423);
text.setColor('#f4e4c1');
});
// Click handler
btn.on('pointerdown', () => {
this.onChoiceSelected(choice);
});
this.choicesContainer.add([btn, text]);
});
}
/**
* Show continue button
*/
showContinueButton(nextNode) {
this.choicesContainer.removeAll(true);
const continueBtn = this.scene.add.text(0, 0, '▼ Continue (SPACE)', {
fontSize: '16px',
fontFamily: 'Georgia, serif',
color: '#888888'
});
continueBtn.setOrigin(0.5);
// Pulse animation
this.scene.tweens.add({
targets: continueBtn,
alpha: 0.3,
duration: 800,
yoyo: true,
repeat: -1
});
this.choicesContainer.add(continueBtn);
// Space key or click to continue
const spaceKey = this.scene.input.keyboard.addKey('SPACE');
spaceKey.once('down', () => {
if (nextNode === 'END') {
this.endDialogue();
} else {
this.showNode(nextNode);
}
});
}
/**
* Handle choice selection
*/
onChoiceSelected(choice) {
console.log(`💬 Choice selected: ${choice.text}`);
// Execute choice action
if (choice.action) {
this.executeNodeAction(choice.action);
}
// Callback
if (this.onChoiceMade) {
this.onChoiceMade(choice);
}
// Go to next node
if (choice.next) {
this.showNode(choice.next);
} else {
this.endDialogue();
}
}
/**
* Execute node action
*/
executeNodeAction(action) {
switch (action.type) {
case 'quest_start':
this.scene.questSystem?.startQuest(action.questId);
break;
case 'quest_complete':
this.scene.questSystem?.completeQuest(action.questId);
break;
case 'give_item':
this.scene.inventorySystem?.addItem(action.itemId, action.amount);
break;
case 'take_item':
this.scene.inventorySystem?.removeItem(action.itemId, action.amount);
break;
case 'relationship_change':
this.changeRelationship(action.npcId, action.amount);
break;
case 'custom':
if (action.callback) {
action.callback(this.scene);
}
break;
}
}
/**
* Check if condition is met
*/
checkCondition(condition) {
switch (condition.type) {
case 'quest_active':
return this.scene.questSystem?.isQuestActive(condition.questId);
case 'quest_complete':
return this.scene.questSystem?.isQuestComplete(condition.questId);
case 'has_item':
return this.scene.inventorySystem?.hasItem(condition.itemId, condition.amount);
case 'relationship':
return this.getRelationship(condition.npcId) >= condition.value;
case 'custom':
return condition.check(this.scene);
default:
return true;
}
}
/**
* Add to conversation history
*/
addToHistory(nodeId, text) {
const speakerId = this.currentSpeaker.id || this.currentSpeaker.name;
if (!this.dialogueHistory.has(speakerId)) {
this.dialogueHistory.set(speakerId, []);
}
this.dialogueHistory.get(speakerId).push({
nodeId: nodeId,
text: text,
timestamp: Date.now()
});
}
/**
* Relationship tracking
*/
changeRelationship(npcId, amount) {
// TODO: Integrate with proper relationship system
console.log(`💕 Relationship with ${npcId}: ${amount > 0 ? '+' : ''}${amount}`);
}
getRelationship(npcId) {
// TODO: Get from relationship system
return 0;
}
/**
* End dialogue
*/
endDialogue() {
console.log('💬 Dialogue ended');
// Stop typewriter effect if running
if (this.typewriterTimer) {
this.typewriterTimer.remove();
this.typewriterTimer = null;
}
// Clean up UI
if (this.dialogueContainer) {
this.dialogueContainer.destroy();
}
// Resume game
this.scene.physics.resume();
// Callback
if (this.onDialogueComplete) {
this.onDialogueComplete();
}
// Reset state
this.currentDialogue = null;
this.currentSpeaker = null;
this.currentNode = null;
this.onDialogueComplete = null;
}
/**
* Check if dialogue is active
*/
isActive() {
return this.currentDialogue !== null;
}
}

View File

@@ -0,0 +1,371 @@
/**
* MainQuestAnaSystem.js
* =====================
* KRVAVA ŽETEV - Main Quest: Finding Ana (Phase 42)
*
* Features:
* - Main story quest (finding lost sister)
* - 4 Acts + Finale
* - Quest journal & tracking
* - Lore entries discovery
* - Multiple endings
* - Decision system
*
* @author NovaFarma Team
* @date 2025-12-23
*/
export default class MainQuestAnaSystem {
constructor(scene) {
this.scene = scene;
// Quest state
this.currentAct = 0;
this.currentQuest = null;
this.completedQuests = new Set();
// Story decisions
this.decisions = new Map();
this.moralityScore = 0; // -100 (revenge) to +100 (cure)
// Lore entries
this.loreEntries = new Map();
this.discoveredLore = new Set();
// Quest journal
this.journal = [];
console.log('👧 MainQuestAnaSystem initialized');
// Register all quests
this.registerQuests();
// Register lore entries
this.registerLoreEntries();
}
/**
* Register all main quests
*/
registerQuests() {
// Prologue is handled by PrologueScene
// ACT 1: Searching for Clues (Already in Act1QuestData.js)
// Quests 1.1 - 1.8 are integrated
// ACT 2: Discovering the Lab
this.registerQuest('2.1', {
act: 2,
name: 'The Underground Facility',
description: 'Twin Bond led you to old military bunker. Investigate.',
objectives: [
{ id: 'find_bunker', desc: 'Locate underground bunker entrance', type: 'location' },
{ id: 'clear_entrance', desc: 'Clear zombie blockade', type: 'combat' },
{ id: 'enter_lab', desc: 'Enter Black Serpent Laboratory', type: 'location' }
],
rewards: { xp: 2000, zlatniki: 1000, bondStrength: 15 }
});
this.registerQuest('2.2', {
act: 2,
name: 'Lab Records',
description: 'Search lab computers for information about Ana.',
objectives: [
{ id: 'find_terminal', desc: 'Find working computer terminal', type: 'location' },
{ id: 'hack_system', desc: 'Hack into lab database', type: 'puzzle' },
{ id: 'read_files', desc: 'Read experiment logs', type: 'interact' }
],
rewards: { xp: 1500, lore: ['virus_origin', 'experiment_logs'] }
});
this.registerQuest('2.3', {
act: 2,
name: 'Zombie Test Subjects',
description: 'Discover horrifying truth about zombie experiments.',
objectives: [
{ id: 'find_cells', desc: 'Locate holding cells', type: 'location' },
{ id: 'read_charts', desc: 'Read patient charts', type: 'interact' },
{ id: 'find_ana_cell', desc: 'Find Ana\'s empty cell', type: 'location' }
],
rewards: { xp: 2500, bondStrength: 20, lore: ['experiment_subjects'] }
});
// ACT 3: Confronting the Truth
this.registerQuest('3.1', {
act: 3,
name: 'The Scientist',
description: 'Track down lead scientist Dr. Kovač.',
objectives: [
{ id: 'find_hideout', desc: 'Locate Dr. Kovač\'s hideout', type: 'location' },
{ id: 'confront_scientist', desc: 'Confront Dr. Kovač', type: 'dialogue' },
{ id: 'make_choice', desc: 'Kill or spare the scientist?', type: 'decision' }
],
rewards: { xp: 3000, decision: 'scientist_fate' }
});
this.registerQuest('3.2', {
act: 3,
name: 'The Final Obstacle',
description: 'Ana is held in the mountain vault. Prepare for the breach.',
objectives: [
{ id: 'gather_allies', desc: 'Recruit allies for the assault', type: 'social' },
{ id: 'craft_weapons', desc: 'Craft high-tier equipment', type: 'crafting' },
{ id: 'locate_lair', desc: 'Find the mountain fortress', type: 'location' }
],
rewards: { xp: 4000, zlatniki: 5000 }
});
this.registerQuest('3.3', {
act: 3,
name: 'The Cure Research',
description: 'Find and study cure research notes.',
objectives: [
{ id: 'find_notes', desc: 'Collect all 7 research notes', type: 'collect' },
{ id: 'study_cure', desc: 'Study cure methodology', type: 'research' },
{ id: 'make_choice', desc: 'Pursue cure or revenge?', type: 'decision' }
],
rewards: { xp: 3500, decision: 'cure_or_revenge', lore: ['cure_research'] }
});
// FINALE
this.registerQuest('finale', {
act: 4,
name: 'Confrontation',
description: 'Reach the reactor core and save Ana.',
objectives: [
{ id: 'enter_lair', desc: 'Storm the mountain fortress', type: 'location' },
{ id: 'defeat_boss', desc: 'Breach the vault defenses', type: 'boss_fight' },
{ id: 'save_ana', desc: 'Rescue Ana', type: 'cutscene' },
{ id: 'final_choice', desc: 'The ultimate decision...', type: 'decision' }
],
rewards: { xp: 10000, achievement: 'main_quest_complete' }
});
console.log('✅ Main quest chain registered');
}
/**
* Register single quest
*/
registerQuest(id, data) {
// Quests are managed by QuestSystemExpanded
// This just tracks main quest progress
console.log(`📋 Quest ${id}: ${data.name}`);
}
/**
* Register lore entries
*/
registerLoreEntries() {
this.loreEntries.set('virus_origin', {
title: 'The Outbreak',
content: `BLACK SERPENT INITIATIVE - CLASSIFIED
Project Lazarus began as a cure for death itself. Using ancient viral samples
recovered from permafrost, Dr. Helena Kovač believed she could reanimate dead
tissue while preserving consciousness.
The first trials were... promising. Deceased subjects showed signs of life.
But something went wrong. The virus mutated. Subjects became aggressive,
hungry for living flesh.
We tried to contain it. We failed.
The Outbreak began here, in this very laboratory, on October 13th, 2024.
May God forgive us.`,
location: 'Lab Computer Terminal 1'
});
this.loreEntries.set('experiment_logs', {
title: 'Subject Ana Kovač',
content: `EXPERIMENT LOG #247
Subject: Ana Kovač (Age 19, Twin Sister of Kai Kovač)
Status: SPECIAL CASE
Subject shows unprecedented resistance to viral infection. Twin Bond
phenomenon observed - psychic connection to brother remains strong even
in experimental conditions.
Dr. Kovač (mother) has requested subject be moved to special containment.
Krnić has shown interest in subject. Authorization pending.
UPDATE: Subject escaped during the facility breach. Current location unknown.
Twin Bond suggests she's alive. Brother searching for her.`,
location: 'Lab Record Room'
});
this.loreEntries.set('project_apex', {
title: 'Project Apex',
content: `PROJECT: APEX PREDATOR
Combining viral genetics (recovered from permafrost) with the Alpha Hybrid
strain has created our most powerful weapon yet.
Designation: ALPHA TROLL KING
Capabilities:
- Enhanced strength (100x human)
- Accelerated healing
- Pack leader pheromones (controls all lesser infected)
- High intelligence (strategic thinking)
WARNING: Subject is unstable. Uncontrollable by anyone but Krnić.
Recommendation: DEPLOYMENT
The new world requires a king.`,
location: 'Lab Chief Scientist Office'
});
this.loreEntries.set('cure_research', {
title: 'The Cure - Final Notes',
content: `I've done it. The cure is real.
Using Ana's unique antibodies and the original viral strain, I've synthesized
a counter-agent. It reverses the infection. Restores humanity.
But there's a catch. The cure requires Ana's living tissue. A piece of her
brain stem, specifically. The procedure would kill her.
Kai will come for his sister. When he does, he'll have a choice:
- Save Ana, doom the world to eternal infection
- Sacrifice Ana, cure humanity, lose his sister forever
I can't make this choice. Neither could any parent.
So I've hidden the cure formula. Seven pieces, scattered across the land.
Let fate decide.
- Dr. Helena Kovač`,
location: 'Hidden Lab Vault'
});
console.log(`✅ Registered ${this.loreEntries.size} lore entries`);
}
/**
* Discover lore entry
*/
discoverLore(loreId) {
const lore = this.loreEntries.get(loreId);
if (!lore) {
console.error(`Lore ${loreId} not found!`);
return false;
}
if (this.discoveredLore.has(loreId)) {
console.log(`Already discovered: ${lore.title}`);
return false;
}
this.discoveredLore.add(loreId);
console.log(`📖 LORE DISCOVERED: ${lore.title}`);
console.log(lore.content);
this.showNotification({
title: 'Lore Entry Discovered!',
text: `📖 ${lore.title}`,
icon: '📜'
});
// Add to journal
this.journal.push({
type: 'lore',
id: loreId,
title: lore.title,
date: new Date()
});
return true;
}
/**
* Make story decision
*/
makeDecision(decisionId, choice) {
this.decisions.set(decisionId, choice);
console.log(`⚖️ Decision: ${decisionId} = ${choice}`);
// Track morality
const moralityChanges = {
scientist_fate: { kill: -20, spare: +20 },
cure_or_revenge: { cure: +50, revenge: -50 },
final_choice: { save_ana: -100, sacrifice_ana: +100 }
};
const change = moralityChanges[decisionId]?.[choice] || 0;
this.moralityScore = Math.max(-100, Math.min(100, this.moralityScore + change));
console.log(` Morality: ${this.moralityScore}`);
return true;
}
/**
* Get ending based on decisions
*/
getEnding() {
const savedAna = this.decisions.get('final_choice') === 'save_ana';
const pursuesCure = this.decisions.get('cure_or_revenge') === 'cure';
const sparedScientist = this.decisions.get('scientist_fate') === 'spare';
// 4 Main Endings
if (savedAna && pursuesCure && sparedScientist) {
return {
id: 'perfect_ending',
name: 'The Hope',
description: 'You saved Ana and found another way to create the cure. Humanity has hope.',
icon: '🌟'
};
} else if (!savedAna && pursuesCure) {
return {
id: 'sacrifice_ending',
name: 'The Martyr',
description: 'You sacrificed Ana to cure humanity. The world is saved, but at what cost?',
icon: '😢'
};
} else if (savedAna && !pursuesCure) {
return {
id: 'selfish_ending',
name: 'The Bond',
description: 'You saved Ana but abandoned the cure. The world remains infected, but your sister lives.',
icon: '💔'
};
} else {
return {
id: 'dark_ending',
name: 'The Revenge',
description: 'You chose revenge over everything. Krnić is dead, but Ana and the cure are lost.',
icon: '⚔️'
};
}
}
/**
* Get quest journal
*/
getJournal() {
return this.journal;
}
/**
* Get all discovered lore
*/
getDiscoveredLore() {
return Array.from(this.discoveredLore).map(id => this.loreEntries.get(id));
}
/**
* Helper: Show notification
*/
showNotification(notification) {
console.log(`📢 ${notification.icon} ${notification.title}: ${notification.text}`);
const ui = this.scene.scene.get('UIScene');
if (ui && ui.showNotification) {
ui.showNotification(notification);
}
}
}

View File

@@ -0,0 +1,446 @@
/**
* MASTER GAME SYSTEMS MANAGER
* Centralized coordinator for all game systems
* Created: January 4, 2026
*
* Integrates:
* - Sleep System
* - Crafting Tables System
* - Bakery Shop System
* - Barber Shop System
* - Lawyer Office System
* - Zombie Miner Automation System
* - Town Growth System
* - NPC Privacy System
* - Existing Mining System
*/
class MasterGameSystemsManager {
constructor(game) {
this.game = game;
this.scene = game.scene;
console.log('🎮 Initializing Master Game Systems Manager...');
// Initialize all systems
this.initializeSystems();
// Set up cross-system event listeners
this.setupEventListeners();
console.log('✅ Master Game Systems Manager initialized!');
}
/**
* Initialize all game systems
*/
initializeSystems() {
// HOME & SLEEP
this.sleepSystem = new SleepSystem(this.game);
console.log(' ✓ Sleep System');
// CRAFTING
this.craftingSystem = new CraftingTablesSystem(this.game);
console.log(' ✓ Crafting Tables System');
// TOWN BUILDINGS
this.bakerySystem = new BakeryShopSystem(this.game);
console.log(' ✓ Bakery Shop System');
this.barberSystem = new BarberShopSystem(this.game);
console.log(' ✓ Barber Shop System');
this.lawyerSystem = new LawyerOfficeSystem(this.game);
console.log(' ✓ Lawyer Office System');
// MINING & AUTOMATION
this.zombieMinerSystem = new ZombieMinerAutomationSystem(this.game);
console.log(' ✓ Zombie Miner Automation System');
// TOWN GROWTH
this.townGrowthSystem = new TownGrowthSystem(this.game);
console.log(' ✓ Town Growth System');
// NPC SYSTEMS
this.npcPrivacySystem = new NPCPrivacySystem(this.game);
console.log(' ✓ NPC Privacy System');
// Register all systems globally
this.registerSystems();
}
/**
* Register systems to game registry
*/
registerSystems() {
this.game.registry.set('sleepSystem', this.sleepSystem);
this.game.registry.set('craftingSystem', this.craftingSystem);
this.game.registry.set('bakerySystem', this.bakerySystem);
this.game.registry.set('barberSystem', this.barberSystem);
this.game.registry.set('lawyerSystem', this.lawyerSystem);
this.game.registry.set('zombieMinerSystem', this.zombieMinerSystem);
this.game.registry.set('townGrowthSystem', this.townGrowthSystem);
this.game.registry.set('npcPrivacySystem', this.npcPrivacySystem);
console.log(' ✓ All systems registered to game registry');
}
/**
* Set up cross-system event listeners
*/
setupEventListeners() {
// TOWN GROWTH → SERVICES
this.game.events.on('serviceUnlocked', (data) => {
this.onServiceUnlocked(data);
});
// MARRIAGE → LAWYER AUTO-UNLOCK
this.game.events.on('marriageComplete', () => {
this.lawyerSystem.checkAutoUnlock();
});
// RELATIONSHIP CHANGE → LAWYER AUTO-UNLOCK
this.game.events.on('relationshipChanged', (data) => {
if (data.hearts <= 3) {
this.lawyerSystem.checkAutoUnlock();
}
});
// BUILDING UNLOCKED → TOWN GROWTH
this.game.events.on('buildingUnlocked', (data) => {
this.townGrowthSystem.checkPopulationUnlocks();
});
// ZOMBIE HIRED → TOWN GROWTH CHECK
this.game.events.on('zombieWorkerHired', () => {
this.townGrowthSystem.checkPopulationUnlocks();
});
// SLEEP → ZOMBIE LOYALTY DECAY PAUSE
this.game.events.on('sleepStarted', () => {
this.pauseZombieLoyaltyDecay = true;
});
this.game.events.on('wakeUp', () => {
this.pauseZombieLoyaltyDecay = false;
});
console.log(' ✓ Cross-system event listeners configured');
}
/**
* Handle service unlock
*/
onServiceUnlocked(data) {
const { serviceId, population } = data;
console.log(`🏛️ Service unlocked: ${serviceId} at pop ${population}`);
// Trigger service-specific initialization
switch (serviceId) {
case 'market':
this.initializeMarket();
break;
case 'hospital':
this.initializeHospital();
break;
case 'school':
this.initializeSchool();
break;
case 'bank':
this.initializeBank();
break;
case 'museum':
this.initializeMuseum();
break;
case 'theater':
this.initializeTheater();
break;
}
}
/**
* Service initializers (placeholders for future implementation)
*/
initializeMarket() {
console.log(' → Market initialized');
// TODO: Implement market system
}
initializeHospital() {
console.log(' → Hospital initialized');
// TODO: Implement hospital system
}
initializeSchool() {
console.log(' → School initialized');
// TODO: Implement school system
}
initializeBank() {
console.log(' → Bank initialized');
// TODO: Implement bank system
}
initializeMuseum() {
console.log(' → Museum initialized');
// TODO: Implement museum system
}
initializeTheater() {
console.log(' → Theater initialized');
// TODO: Implement theater system
}
/**
* Update all systems (called every frame)
*/
update(time, delta) {
const deltaSeconds = delta / 1000;
// Update systems that need per-frame updates
this.sleepSystem.update(deltaSeconds);
this.craftingSystem.update(deltaSeconds);
// Update zombie miners (if not paused by sleep)
if (!this.pauseZombieLoyaltyDecay) {
this.zombieMinerSystem.update(deltaSeconds);
}
}
/**
* Hourly update (called when game hour changes)
*/
onHourChange(hour) {
// Update time-sensitive systems
this.bakerySystem.update();
this.townGrowthSystem.update();
// Check for automation collection reminders
if (hour % 4 === 0) { // Every 4 hours
this.checkAutomationReminders();
}
}
/**
* Check automation reminders
*/
checkAutomationReminders() {
// Zombie miner automation
if (this.zombieMinerSystem.automationActive) {
const hours = this.zombieMinerSystem.getHoursSinceLastCollection();
if (hours >= 8) {
this.game.showNotification({
title: '⛏️ Automation Ready',
text: `${hours.toFixed(0)}h of mining ready to collect!`,
icon: '⛏️'
});
}
}
}
/**
* Daily update (called at midnight)
*/
onDayChange(day) {
console.log(`📅 Day ${day} - Running daily system updates...`);
// Town growth check
this.townGrowthSystem.update();
// Restock shops
this.bakerySystem.restockInventory();
// Birthday cake deliveries
this.bakerySystem.checkBirthdayCakeDeliveries();
}
/**
* Save all systems state
*/
saveAllSystems() {
return {
version: '1.0',
timestamp: Date.now(),
systems: {
sleep: {
playerBed: this.sleepSystem.playerBed,
unlockedBeds: Object.entries(this.sleepSystem.bedTypes)
.filter(([_, bed]) => bed.unlocked)
.map(([key, _]) => key)
},
crafting: {
currentTable: this.craftingSystem.currentTable.id,
unlockedRecipes: this.craftingSystem.unlockedRecipes,
largeTableUnlocked: this.craftingSystem.tables.LARGE.unlocked
},
bakery: {
isUnlocked: this.bakerySystem.isUnlocked,
inventory: this.bakerySystem.inventory,
birthdayOrders: this.bakerySystem.birthdayCakeOrders
},
barber: {
isUnlocked: this.barberSystem.isUnlocked,
playerAppearance: this.barberSystem.playerAppearance,
savedLooks: this.barberSystem.savedLooks,
visitCount: this.barberSystem.visitCount
},
lawyer: {
isUnlocked: this.lawyerSystem.isUnlocked,
hasPrenup: this.lawyerSystem.hasPrenup,
divorceHistory: this.lawyerSystem.divorceHistory,
counselingInProgress: this.lawyerSystem.counselingInProgress
},
zombieMiners: {
miners: this.zombieMinerSystem.zombieMiners,
equipment: this.zombieMinerSystem.zombieEquipment,
lastCollectionTime: this.zombieMinerSystem.lastCollectionTime
},
townGrowth: {
population: this.townGrowthSystem.population,
populationSlots: this.townGrowthSystem.populationSlots,
villages: this.townGrowthSystem.villages,
services: this.townGrowthSystem.services
},
npcPrivacy: {
npcHomes: this.npcPrivacySystem.npcHomes,
visitHistory: this.npcPrivacySystem.visitHistory,
lastVisitTimes: this.npcPrivacySystem.lastVisitTimes
}
}
};
}
/**
* Load all systems state
*/
loadAllSystems(saveData) {
if (!saveData || !saveData.systems) {
console.warn('No save data to load');
return false;
}
try {
const systems = saveData.systems;
// Load sleep system
if (systems.sleep) {
systems.sleep.unlockedBeds.forEach(bedKey => {
this.sleepSystem.bedTypes[bedKey].unlocked = true;
});
}
// Load crafting system
if (systems.crafting) {
this.craftingSystem.tables.LARGE.unlocked = systems.crafting.largeTableUnlocked;
this.craftingSystem.unlockedRecipes = systems.crafting.unlockedRecipes || [];
}
// Load bakery
if (systems.bakery) {
this.bakerySystem.isUnlocked = systems.bakery.isUnlocked;
this.bakerySystem.birthdayCakeOrders = systems.bakery.birthdayOrders || [];
}
// Load barber
if (systems.barber) {
this.barberSystem.isUnlocked = systems.barber.isUnlocked;
this.barberSystem.playerAppearance = systems.barber.playerAppearance;
this.barberSystem.savedLooks = systems.barber.savedLooks || [];
this.barberSystem.visitCount = systems.barber.visitCount || 0;
}
// Load lawyer
if (systems.lawyer) {
this.lawyerSystem.isUnlocked = systems.lawyer.isUnlocked;
this.lawyerSystem.hasPrenup = systems.lawyer.hasPrenup || false;
this.lawyerSystem.divorceHistory = systems.lawyer.divorceHistory || [];
this.lawyerSystem.counselingInProgress = systems.lawyer.counselingInProgress || false;
}
// Load zombie miners
if (systems.zombieMiners) {
this.zombieMinerSystem.zombieMiners = systems.zombieMiners.miners || [];
this.zombieMinerSystem.zombieEquipment = systems.zombieMiners.equipment || {};
this.zombieMinerSystem.lastCollectionTime = systems.zombieMiners.lastCollectionTime;
this.zombieMinerSystem.updateAutomationYield();
}
// Load town growth
if (systems.townGrowth) {
this.townGrowthSystem.population = systems.townGrowth.population;
this.townGrowthSystem.populationSlots = systems.townGrowth.populationSlots;
this.townGrowthSystem.villages = systems.townGrowth.villages || [];
this.townGrowthSystem.services = systems.townGrowth.services || {};
}
// Load NPC privacy
if (systems.npcPrivacy) {
this.npcPrivacySystem.npcHomes = systems.npcPrivacy.npcHomes || {};
this.npcPrivacySystem.visitHistory = systems.npcPrivacy.visitHistory || {};
this.npcPrivacySystem.lastVisitTimes = systems.npcPrivacy.lastVisitTimes || {};
}
console.log('✅ All systems loaded from save data');
return true;
} catch (error) {
console.error('❌ Error loading systems:', error);
return false;
}
}
/**
* Get all systems status (for debug/admin panel)
*/
getAllSystemsStatus() {
return {
sleep: {
active: true,
currentBed: this.sleepSystem.playerBed.name,
isSleeping: this.sleepSystem.isSleeping
},
crafting: {
active: true,
currentTable: this.craftingSystem.currentTable.name,
isCrafting: this.craftingSystem.isCrafting,
queueLength: this.craftingSystem.craftingQueue.length
},
bakery: {
active: this.bakerySystem.isUnlocked,
isOpen: this.bakerySystem.isOpen,
stockLevel: Object.values(this.bakerySystem.inventory)
.reduce((sum, item) => sum + item.stock, 0)
},
barber: {
active: this.barberSystem.isUnlocked,
isOpen: this.barberSystem.isOpen,
currentStyle: this.barberSystem.playerAppearance.hairstyle
},
lawyer: {
active: this.lawyerSystem.isUnlocked,
counselingActive: this.lawyerSystem.counselingInProgress,
hasPrenup: this.lawyerSystem.hasPrenup
},
zombieMiners: {
active: this.zombieMinerSystem.automationActive,
minerCount: this.zombieMinerSystem.zombieMiners.length,
yieldPerHour: this.zombieMinerSystem.totalYieldPerHour
},
townGrowth: {
active: true,
population: this.townGrowthSystem.population,
maxPopulation: this.townGrowthSystem.maxPopulation,
status: this.townGrowthSystem.getTownStatus()
},
npcPrivacy: {
active: true,
homesGenerated: Object.keys(this.npcPrivacySystem.npcHomes).length
}
};
}
}

View File

@@ -0,0 +1,506 @@
/**
* STORY & QUEST SYSTEM
* Complete quest system with story acts, dialogue, cutscenes, and multiple endings
*/
class StoryQuestSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Quests
this.quests = new Map();
this.activeQuests = new Set();
this.completedQuests = new Set();
// Story progress
this.currentAct = 1;
this.storyFlags = new Set();
// Characters
this.characters = new Map();
// Dialogue
this.currentDialogue = null;
// Endings
this.playerChoices = [];
this.ending = null;
this.loadProgress();
this.init();
console.log('✅ Story & Quest System initialized');
}
init() {
this.defineCharacters();
this.defineQuests();
console.log('📖 Story & Quest system ready');
}
// ========== CHARACTERS ==========
defineCharacters() {
this.characters.set('jakob', {
name: 'Old Jakob',
role: 'Merchant',
relationship: 0,
dialogues: new Map()
});
this.characters.set('lyra', {
name: 'Lyra',
role: 'Mutant Elf',
relationship: 0,
dialogues: new Map()
});
this.characters.set('grok', {
name: 'Grok',
role: 'Troll Guardian',
relationship: 0,
dialogues: new Map()
});
this.characters.set('dr_chen', {
name: 'Dr. Chen',
role: 'Radio Voice',
relationship: 0,
dialogues: new Map()
});
}
// ========== QUESTS ==========
defineQuests() {
// ACT 1: Survival (Day 1-10)
this.defineQuest('first_harvest', {
name: 'Prvi Pridelek',
act: 1,
description: 'Harvest 10 wheat to survive',
objectives: [
{ type: 'harvest', item: 'wheat', amount: 10, current: 0 }
],
rewards: { xp: 100, item: 'iron_hoe' },
unlocks: ['safe_haven']
});
this.defineQuest('safe_haven', {
name: 'Varno Zatočišče',
act: 1,
description: 'Build fence around farm',
objectives: [
{ type: 'build', item: 'fence', amount: 20, current: 0 }
],
rewards: { xp: 150, blueprint: 'reinforced_fence' },
unlocks: ['night_watch']
});
this.defineQuest('night_watch', {
name: 'Nočna Straža',
act: 1,
description: 'Survive first zombie night',
objectives: [
{ type: 'survive', nights: 1, current: 0 }
],
rewards: { xp: 200, item: 'torch_pack' },
unlocks: ['meet_merchant']
});
this.defineQuest('meet_merchant', {
name: 'Meet the Merchant',
act: 1,
description: 'Find Jakob the trader',
objectives: [
{ type: 'talk', npc: 'jakob' }
],
rewards: { xp: 100, unlocks: 'trading' },
unlocks: ['strange_transmission']
});
// ACT 2: Discovery (Day 11-20)
this.defineQuest('strange_transmission', {
name: 'Strange Transmission',
act: 2,
description: 'Find radio in city',
objectives: [
{ type: 'find', item: 'radio', location: 'city_ruins' }
],
rewards: { xp: 300, item: 'radio' },
unlocks: ['first_tame']
});
this.defineQuest('first_tame', {
name: 'Prvi Poskus',
act: 2,
description: 'Tame first zombie',
objectives: [
{ type: 'tame', creature: 'zombie', amount: 1 }
],
rewards: { xp: 250, unlocks: 'zombie_workers' },
unlocks: ['lab_ruins']
});
this.defineQuest('lab_ruins', {
name: 'Lab Ruins',
act: 2,
description: 'Explore abandoned research facility',
objectives: [
{ type: 'explore', location: 'research_lab' }
],
rewards: { xp: 400, item: 'lab_key' },
unlocks: ['mutant_contact']
});
this.defineQuest('mutant_contact', {
name: 'Mutant Contact',
act: 2,
description: 'Meet friendly mutant Lyra',
objectives: [
{ type: 'talk', npc: 'lyra' }
],
rewards: { xp: 300, unlocks: 'mutation_research' },
unlocks: ['lab_notes']
});
// ACT 3: The Truth (Day 21-30)
this.defineQuest('lab_notes', {
name: 'Lab Notes',
act: 3,
description: 'Collect 5 research documents',
objectives: [
{ type: 'collect', item: 'research_document', amount: 5, current: 0 }
],
rewards: { xp: 500 },
unlocks: ['patient_zero']
});
this.defineQuest('patient_zero', {
name: 'Patient Zero',
act: 3,
description: 'Find virus source',
objectives: [
{ type: 'find', item: 'virus_sample', location: 'deep_lab' }
],
rewards: { xp: 600 },
unlocks: ['difficult_choice']
});
this.defineQuest('difficult_choice', {
name: 'Difficult Choice',
act: 3,
description: 'Choose faction',
objectives: [
{ type: 'choice', options: ['human', 'zombie', 'hybrid'] }
],
rewards: { xp: 1000 },
unlocks: ['final_confrontation']
});
this.defineQuest('final_confrontation', {
name: 'Final Confrontation',
act: 3,
description: 'Boss battle',
objectives: [
{ type: 'defeat', boss: 'zombie_king' }
],
rewards: { xp: 2000 },
ending: true
});
}
defineQuest(id, data) {
this.quests.set(id, {
id,
active: false,
completed: false,
...data
});
}
// ========== QUEST MANAGEMENT ==========
startQuest(questId) {
const quest = this.quests.get(questId);
if (!quest || quest.active || quest.completed) return false;
quest.active = true;
this.activeQuests.add(questId);
console.log(`📜 Quest started: ${quest.name}`);
return true;
}
updateQuestProgress(questId, objectiveIndex, progress) {
const quest = this.quests.get(questId);
if (!quest || !quest.active) return;
const objective = quest.objectives[objectiveIndex];
if (!objective) return;
objective.current = progress;
// Check if objective complete
if (this.isObjectiveComplete(objective)) {
console.log(`✅ Objective complete: ${objective.type}`);
// Check if all objectives complete
if (quest.objectives.every(obj => this.isObjectiveComplete(obj))) {
this.completeQuest(questId);
}
}
}
isObjectiveComplete(objective) {
switch (objective.type) {
case 'harvest':
case 'build':
case 'collect':
return objective.current >= objective.amount;
case 'talk':
case 'find':
case 'explore':
case 'defeat':
return objective.current === true;
case 'survive':
return objective.current >= objective.nights;
default:
return false;
}
}
completeQuest(questId) {
const quest = this.quests.get(questId);
if (!quest) return;
quest.active = false;
quest.completed = true;
this.activeQuests.delete(questId);
this.completedQuests.add(questId);
// Grant rewards
this.grantQuestRewards(quest);
// Unlock next quests
if (quest.unlocks) {
const unlocks = Array.isArray(quest.unlocks) ? quest.unlocks : [quest.unlocks];
unlocks.forEach(nextQuest => this.startQuest(nextQuest));
}
// Check for ending
if (quest.ending) {
this.triggerEnding();
}
console.log(`🎉 Quest completed: ${quest.name}!`);
this.saveProgress();
}
grantQuestRewards(quest) {
const rewards = quest.rewards;
if (rewards.xp && this.scene.skillTree) {
this.scene.skillTree.addXP(rewards.xp);
}
if (rewards.item && this.scene.inventorySystem) {
this.scene.inventorySystem.addItem(rewards.item, 1);
}
if (rewards.blueprint) {
console.log(`📋 Unlocked blueprint: ${rewards.blueprint}`);
}
}
// ========== DIALOGUE SYSTEM ==========
startDialogue(npcId, dialogueId) {
const character = this.characters.get(npcId);
if (!character) return false;
this.currentDialogue = {
npc: npcId,
dialogueId,
currentNode: 0,
choices: []
};
console.log(`💬 Dialogue started with ${character.name}`);
return true;
}
selectDialogueChoice(choiceIndex) {
if (!this.currentDialogue) return;
const choice = this.currentDialogue.choices[choiceIndex];
if (!choice) return;
// Track player choice
this.playerChoices.push({
npc: this.currentDialogue.npc,
choice: choice.text,
consequence: choice.consequence
});
// Update relationship
const character = this.characters.get(this.currentDialogue.npc);
if (character && choice.relationshipDelta) {
character.relationship += choice.relationshipDelta;
}
// Apply consequences
if (choice.consequence) {
this.applyChoiceConsequence(choice.consequence);
}
console.log(`💬 Choice selected: ${choice.text}`);
}
applyChoiceConsequence(consequence) {
// Set story flags
if (consequence.flag) {
this.storyFlags.add(consequence.flag);
}
// Unlock quests
if (consequence.unlockQuest) {
this.startQuest(consequence.unlockQuest);
}
// Change faction
if (consequence.faction) {
this.playerChoices.push({ type: 'faction', value: consequence.faction });
}
}
endDialogue() {
this.currentDialogue = null;
}
// ========== CUTSCENES ==========
playCutscene(cutsceneId) {
console.log(`🎬 Playing cutscene: ${cutsceneId}`);
const cutscenes = {
'arrival': this.cutsceneArrival.bind(this),
'first_zombie': this.cutsceneFirstZombie.bind(this),
'city_discovery': this.cutsceneCityDiscovery.bind(this),
'boss_reveal': this.cutsceneBossReveal.bind(this)
};
const cutscene = cutscenes[cutsceneId];
if (cutscene) {
cutscene();
}
}
cutsceneArrival() {
console.log('🎬 Arrival cutscene - Farm overview');
}
cutsceneFirstZombie() {
console.log('🎬 First zombie encounter - Tutorial');
}
cutsceneCityDiscovery() {
console.log('🎬 City discovery - Ruins pan');
}
cutsceneBossReveal() {
console.log('🎬 Boss reveal - Zombie King emergence');
}
// ========== ENDINGS ==========
triggerEnding() {
// Determine ending based on player choices
const faction = this.getPlayerFaction();
const relationships = this.getRelationships();
if (faction === 'human' && relationships.jakob > 50) {
this.ending = 'cure';
this.playCutscene('cure_ending');
} else if (faction === 'zombie') {
this.ending = 'zombie_king';
this.playCutscene('zombie_king_ending');
} else if (faction === 'hybrid') {
this.ending = 'mutation';
this.playCutscene('mutation_ending');
} else if (relationships.lyra > 70) {
this.ending = 'escape';
this.playCutscene('escape_ending');
} else {
this.ending = 'farmer';
this.playCutscene('farmer_ending');
}
console.log(`🎬 Ending: ${this.ending}`);
this.saveProgress();
}
getPlayerFaction() {
const factionChoice = this.playerChoices.find(c => c.type === 'faction');
return factionChoice ? factionChoice.value : null;
}
getRelationships() {
const relationships = {};
for (const [id, character] of this.characters.entries()) {
relationships[id] = character.relationship;
}
return relationships;
}
// ========== PERSISTENCE ==========
saveProgress() {
const data = {
currentAct: this.currentAct,
activeQuests: Array.from(this.activeQuests),
completedQuests: Array.from(this.completedQuests),
storyFlags: Array.from(this.storyFlags),
playerChoices: this.playerChoices,
ending: this.ending,
characters: Array.from(this.characters.entries()).map(([id, char]) => ({
id,
relationship: char.relationship
}))
};
localStorage.setItem('novafarma_story', JSON.stringify(data));
}
loadProgress() {
const saved = localStorage.getItem('novafarma_story');
if (saved) {
try {
const data = JSON.parse(saved);
this.currentAct = data.currentAct || 1;
this.activeQuests = new Set(data.activeQuests || []);
this.completedQuests = new Set(data.completedQuests || []);
this.storyFlags = new Set(data.storyFlags || []);
this.playerChoices = data.playerChoices || [];
this.ending = data.ending || null;
if (data.characters) {
data.characters.forEach(char => {
const character = this.characters.get(char.id);
if (character) {
character.relationship = char.relationship;
}
});
}
console.log('✅ Story progress loaded');
} catch (error) {
console.error('Failed to load story progress:', error);
}
}
}
destroy() {
this.saveProgress();
console.log('📖 Story & Quest System destroyed');
}
}

View File

@@ -0,0 +1,403 @@
/**
* TownRestorationSystem.js
* =========================
* KRVAVA ŽETEV - Complete Town Restoration System (Phase 19)
*
* Features:
* - 27 towns across 18 biomes
* - 150+ ruined buildings
* - 180 total NPCs
* - Building restoration mechanics
* - NPC move-in system
* - Global milestones & rewards
* - Major city endgame project
*
* @author NovaFarma Team
* @date 2025-12-23
*/
class TownRestorationSystem {
constructor(scene) {
this.scene = scene;
// Town registry
this.towns = new Map();
this.buildings = new Map();
this.npcs = new Map();
// Progress tracking
this.buildingsRestored = 0;
this.totalBuildings = 150;
this.npcsRescued = 0;
this.totalNPCs = 180;
this.townsCompleted = 0;
this.totalTowns = 27;
// Milestones
this.milestones = [10, 25, 50, 100, 180];
this.unlockedMilestones = [];
console.log('🏗️ TownRestorationSystem initialized');
// Register all towns
this.registerTowns();
// Register Hope Valley buildings
this.registerHopeValleyBuildings();
}
/**
* Register all towns
*/
registerTowns() {
// Hope Valley (Starting town - 15 buildings)
this.towns.set('hope_valley', {
id: 'hope_valley',
name: 'Hope Valley',
biome: 'grassland',
icon: '🏘️',
totalBuildings: 15,
restoredBuildings: 0,
npcs: 0,
isUnlocked: true,
isCompleted: false
});
// Other towns (6 more buildings each = 156 total)
const otherTowns = [
{ id: 'forest_grove', name: 'Forest Grove', biome: 'forest', buildings: 6 },
{ id: 'desert_oasis', name: 'Desert Oasis', biome: 'desert', buildings: 6 },
{ id: 'frozen_harbor', name: 'Frozen Harbor', biome: 'frozen', buildings: 6 },
{ id: 'volcanic_refuge', name: 'Volcanic Refuge', biome: 'volcanic', buildings: 6 },
{ id: 'coastal_bay', name: 'Coastal Bay', biome: 'beach', buildings: 6 },
{ id: 'mountain_peak', name: 'Mountain Peak', biome: 'mountain', buildings: 6 },
{ id: 'swamp_village', name: 'Swamp Village', biome: 'swamp', buildings: 6 },
{ id: 'crystal_city', name: 'Crystal City', biome: 'crystal', buildings: 8 },
{ id: 'atlantis', name: 'Atlantis', biome: 'underwater', buildings: 10 },
// ... (27 total towns)
];
otherTowns.forEach(town => {
this.towns.set(town.id, {
id: town.id,
name: town.name,
biome: town.biome,
icon: '🏘️',
totalBuildings: town.buildings,
restoredBuildings: 0,
npcs: 0,
isUnlocked: false,
isCompleted: false
});
});
console.log(`✅ Registered ${this.towns.size} towns`);
}
/**
* Register Hope Valley buildings (15)
*/
registerHopeValleyBuildings() {
const buildings = [
// Essential (NPCs' homes)
{ id: 'ivan_house', name: "Ivan's House", npc: 'Ivan', materials: { wood: 100, stone: 50 }, time: 3 },
{ id: 'marija_house', name: "Marija's House", npc: 'Marija', materials: { wood: 100, stone: 50 }, time: 3 },
{ id: 'jakob_shop', name: "Jakob's Shop", npc: 'Jakob', materials: { wood: 150, iron: 30 }, time: 4 },
{ id: 'dr_chen_clinic', name: "Dr. Chen's Clinic", npc: 'Dr. Chen', materials: { wood: 200, stone: 100 }, time: 5 },
{ id: 'lena_bakery', name: "Lena's Bakery", npc: 'Lena', materials: { wood: 120, stone: 60 }, time: 4 },
// Community buildings
{ id: 'town_hall', name: 'Town Hall', materials: { wood: 500, stone: 300, iron: 100 }, time: 10 },
{ id: 'community_center', name: 'Community Center', materials: { wood: 400, stone: 250 }, time: 8 },
{ id: 'library', name: 'Library', materials: { wood: 300, stone: 150 }, time: 6 },
{ id: 'school', name: 'School', materials: { wood: 350, stone: 200 }, time: 7 },
{ id: 'guard_tower', name: 'Guard Tower', materials: { stone: 400, iron: 150 }, time: 8 },
// Utility buildings
{ id: 'warehouse', name: 'Warehouse', materials: { wood: 250, stone: 150 }, time: 5 },
{ id: 'water_tower', name: 'Water Tower', materials: { stone: 300, iron: 100 }, time: 6 },
{ id: 'power_station', name: 'Power Station', materials: { stone: 400, iron: 200, wire: 50 }, time: 9 },
{ id: 'market', name: 'Market Square', materials: { wood: 200, stone: 100 }, time: 5 },
{ id: 'fountain', name: 'Town Fountain', materials: { stone: 150, marble: 50 }, time: 4 }
];
buildings.forEach(building => {
this.buildings.set(building.id, {
...building,
town: 'hope_valley',
isRestored: false,
progress: 0,
workers: [],
startTime: null
});
});
console.log(`✅ Registered ${buildings.length} Hope Valley buildings`);
}
/**
* Start building restoration
*/
startRestoration(buildingId, zombieWorkers = []) {
const building = this.buildings.get(buildingId);
if (!building) {
console.error(`Building ${buildingId} not found!`);
return false;
}
if (building.isRestored) {
console.log(`${building.name} is already restored!`);
return false;
}
// Check materials
if (!this.hasMaterials(building.materials)) {
console.log(`Not enough materials for ${building.name}!`);
return false;
}
// Consume materials
this.consumeMaterials(building.materials);
// Assign workers
building.workers = zombieWorkers;
building.startTime = Date.now();
building.progress = 0;
// Calculate construction time (base time reduced by workers)
const workerBonus = zombieWorkers.length * 0.2; // 20% per worker
const constructionTime = building.time * (1 - workerBonus);
console.log(`🏗️ Started restoring ${building.name} (${constructionTime} days with ${zombieWorkers.length} workers)`);
// Auto-complete after time
setTimeout(() => {
this.completeRestoration(buildingId);
}, constructionTime * 1000 * 60); // Convert to ms (for demo, 1 day = 1 min)
this.showNotification({
title: 'Restoration Started!',
text: `🏗️ ${building.name} - ${constructionTime} days`,
icon: '🔨'
});
return true;
}
/**
* Complete building restoration
*/
completeRestoration(buildingId) {
const building = this.buildings.get(buildingId);
if (!building) return;
building.isRestored = true;
building.progress = 100;
this.buildingsRestored++;
console.log(`${building.name} restored! (${this.buildingsRestored}/${this.totalBuildings})`);
// Move in NPC if building has one
if (building.npc) {
this.moveInNPC(building.npc, building.town);
}
// Update town progress
this.updateTownProgress(building.town);
// Check milestones
this.checkMilestones();
// Update visuals in the current scene
if (this.scene.updateBuildingVisuals) {
this.scene.updateBuildingVisuals(buildingId);
}
this.showNotification({
title: 'Building Complete!',
text: `${building.name} restored!`,
icon: '🏗️'
});
}
/**
* Move in NPC
*/
moveInNPC(npcName, townId) {
const npc = {
name: npcName,
town: townId,
movedInDate: Date.now(),
hearts: 0,
giftsGiven: 0,
questsCompleted: 0
};
this.npcs.set(npcName, npc);
this.npcsRescued++;
console.log(`👤 ${npcName} moved into ${townId}! (${this.npcsRescued}/${this.totalNPCs})`);
// Update town NPC count
const town = this.towns.get(townId);
if (town) {
town.npcs++;
}
this.showNotification({
title: 'NPC Moved In!',
text: `👤 ${npcName} is now living in town!`,
icon: '🏠'
});
}
/**
* Update town progress
*/
updateTownProgress(townId) {
const town = this.towns.get(townId);
if (!town) return;
// Count restored buildings in this town
const townBuildings = Array.from(this.buildings.values())
.filter(b => b.town === townId);
const restored = townBuildings.filter(b => b.isRestored).length;
town.restoredBuildings = restored;
// Check if town is complete
if (restored === town.totalBuildings) {
town.isCompleted = true;
this.townsCompleted++;
console.log(`🏆 ${town.name} 100% COMPLETE! (${this.townsCompleted}/${this.totalTowns})`);
this.showNotification({
title: 'TOWN COMPLETE!',
text: `🏆 ${town.name} fully restored!`,
icon: '🎉'
});
}
}
/**
* Check milestones
*/
checkMilestones() {
this.milestones.forEach(milestone => {
if (this.npcsRescued >= milestone && !this.unlockedMilestones.includes(milestone)) {
this.unlockMilestone(milestone);
}
});
}
/**
* Unlock milestone
*/
unlockMilestone(milestone) {
this.unlockedMilestones.push(milestone);
console.log(`🏆 MILESTONE: ${milestone} NPCs rescued!`);
// Grant rewards
const rewards = this.getMilestoneReward(milestone);
this.showNotification({
title: `MILESTONE: ${milestone} NPCs!`,
text: `🏆 Unlocked: ${rewards.text}`,
icon: '🎉'
});
}
/**
* Get milestone reward
*/
getMilestoneReward(milestone) {
const rewards = {
10: { text: 'Community Center unlocked!', feature: 'community_center' },
25: { text: 'Town Festivals enabled!', feature: 'festivals' },
50: { text: 'Town Guard system!', feature: 'town_guard' },
100: { text: 'Major City project unlocked!', feature: 'major_city' },
180: { text: 'UTOPIA ENDING unlocked!', feature: 'utopia_ending' }
};
return rewards[milestone] || { text: 'Bonus unlocked!' };
}
hasMaterials(materials) {
if (!this.scene.inventorySystem) return true; // Safety fallback
for (const [item, count] of Object.entries(materials)) {
if (!this.scene.inventorySystem.hasItem(item, count)) {
return false;
}
}
return true;
}
consumeMaterials(materials) {
if (!this.scene.inventorySystem) return;
for (const [item, count] of Object.entries(materials)) {
this.scene.inventorySystem.removeItem(item, count);
}
console.log('📦 Materials consumed:', materials);
}
/**
* Get restoration progress
*/
getProgress() {
return {
buildings: {
restored: this.buildingsRestored,
total: this.totalBuildings,
percentage: ((this.buildingsRestored / this.totalBuildings) * 100).toFixed(1)
},
npcs: {
rescued: this.npcsRescued,
total: this.totalNPCs,
percentage: ((this.npcsRescued / this.totalNPCs) * 100).toFixed(1)
},
towns: {
completed: this.townsCompleted,
total: this.totalTowns,
percentage: ((this.townsCompleted / this.totalTowns) * 100).toFixed(1)
},
milestones: this.unlockedMilestones
};
}
/**
* Get town info
*/
getTownInfo(townId) {
return this.towns.get(townId);
}
/**
* Get all towns
*/
getAllTowns() {
return Array.from(this.towns.values());
}
/**
* Get building info
*/
getBuildingInfo(buildingId) {
return this.buildings.get(buildingId);
}
/**
* Helper: Show notification
*/
showNotification(notification) {
console.log(`📢 ${notification.icon} ${notification.title}: ${notification.text}`);
const ui = this.scene.scene.get('UIScene');
if (ui && ui.showNotification) {
ui.showNotification(notification);
}
}
}

View File

@@ -0,0 +1,432 @@
/**
* TwinBondSystem.js
* =================
* KRVAVA ŽETEV - Twin Bond Mechanic (Kai ↔ Ana Connection)
*
* Core Concept:
* Kai and Ana share a psychic bond through the Alfa virus
* As twins, they can:
* - Feel each other's emotions
* - Sense each other's location (vaguely)
* - Communicate telepathically (limited)
* - Share HP/stamina in emergencies
*
* Features:
* - Bond Strength meter (0-100)
* - Telepathic messages from Ana
* - Direction to Ana indicator
* - Twin abilities (heal twin, boost twin, swap positions)
* - Bond events (visions, flashbacks)
* - Ana's status tracking (health, danger level)
*
* @author NovaFarma Team
* @date 2025-12-23
*/
class TwinBondSystem {
constructor(scene) {
this.scene = scene;
// Bond state
this.bondStrength = 75; // Starts strong (0-100)
this.maxBondStrength = 100;
// Ana's state (unknown location)
this.anaState = {
alive: true,
health: 100,
dangerLevel: 0, // 0 = safe, 100 = critical
distance: 5000, // pixels from Kai (initially far)
direction: { x: 1000, y: 1000 }, // General direction
lastMessage: null,
messageTime: null
};
// Bond abilities
this.abilities = {
telepathy: { unlocked: true, cooldown: 0, maxCooldown: 30000 }, // 30s
sensePulse: { unlocked: true, cooldown: 0, maxCooldown: 60000 }, // 1min
emergencyLink: { unlocked: false, cooldown: 0, maxCooldown: 300000 }, // 5min
twinRecall: { unlocked: false, cooldown: 0, maxCooldown: 600000 } // 10min
};
// Messages from Ana (telepathic)
this.messageQueue = [];
this.lastMessageTime = 0;
// UI elements
this.bondUI = null;
// Events
this.bondEvents = this.defineBondEvents();
this.nextEventTime = Date.now() + 60000; // First event in 1 minute
console.log('💞 TwinBondSystem initialized - Bond Strength:', this.bondStrength);
}
/**
* Update bond strength based on actions
*/
update(delta) {
const deltaSeconds = delta / 1000;
// Passive bond decay (small)
this.bondStrength = Math.max(0, this.bondStrength - 0.01 * deltaSeconds);
// Update ability cooldowns
for (const ability in this.abilities) {
if (this.abilities[ability].cooldown > 0) {
this.abilities[ability].cooldown -= delta;
}
}
// Check for random bond events
if (Date.now() > this.nextEventTime) {
this.triggerRandomBondEvent();
this.nextEventTime = Date.now() + Phaser.Math.Between(60000, 180000); // 1-3 min
}
// Update Ana's danger level based on story progress
this.updateAnaDanger(deltaSeconds);
}
/**
* Define bond events (telepathic visions)
*/
defineBondEvents() {
return [
{
id: 'first_contact',
trigger: 'auto',
condition: null,
message: 'Kai... can you hear me? I\'m... somewhere dark...',
emotion: 'worried',
bondChange: +5
},
{
id: 'danger_warning',
trigger: 'danger_high',
condition: () => this.anaState.dangerLevel > 70,
message: 'Brother! They\'re coming for me! Please hurry!',
emotion: 'fear',
bondChange: -10
},
{
id: 'memory_flash',
trigger: 'random',
condition: null,
message: 'Remember when we first discovered the Alfa strain? We were so hopeful...',
emotion: 'sad',
bondChange: +3
},
{
id: 'location_hint',
trigger: 'sense_pulse',
condition: null,
message: 'I can feel concrete walls... cold metal... some kind of facility?',
emotion: 'neutral',
bondChange: +5
},
{
id: 'encouragement',
trigger: 'random',
condition: () => this.scene.player?.hp < 50,
message: 'Stay strong, Kai! I believe in you!',
emotion: 'determined',
bondChange: +10
}
];
}
/**
* Trigger a random bond event
*/
triggerRandomBondEvent() {
const randomEvents = this.bondEvents.filter(e => e.trigger === 'random');
if (randomEvents.length === 0) return;
const event = Phaser.Utils.Array.GetRandom(randomEvents);
// Check condition
if (event.condition && !event.condition()) {
return;
}
this.showTelepathicMessage(event.message, event.emotion);
this.changeBondStrength(event.bondChange);
}
/**
* Show telepathic message from Ana
*/
showTelepathicMessage(message, emotion = 'neutral') {
console.log(`💭 Twin Bond Message: ${message}`);
// Update Ana's last message
this.anaState.lastMessage = message;
this.anaState.messageTime = Date.now();
// Show in dialogue system
if (this.scene.dialogueSystem) {
const anaData = {
name: 'Ana (Twin Bond)',
id: 'ana_telepathy'
};
// Create temporary dialogue
const telepathyDialogue = {
root: 'message',
nodes: {
'message': {
speaker: 'Ana (Twin Bond)',
emotion: emotion,
text: message,
next: null // Auto-close
}
}
};
this.scene.dialogueSystem.registerDialogue('telepathy_' + Date.now(), telepathyDialogue);
this.scene.dialogueSystem.startDialogue('telepathy_' + Date.now(), anaData);
}
// Visual effect (bond pulse)
this.scene.cameras.main.flash(500, 150, 100, 255, false);
// Bond strength change
this.showBondPulse();
}
/**
* Change bond strength
*/
changeBondStrength(amount) {
this.bondStrength = Phaser.Math.Clamp(
this.bondStrength + amount,
0,
this.maxBondStrength
);
console.log(`💞 Bond Strength: ${this.bondStrength.toFixed(1)}% (${amount > 0 ? '+' : ''}${amount})`);
// Notify player
if (amount > 0) {
this.scene.events.emit('bondStrengthened', { strength: this.bondStrength });
} else {
this.scene.events.emit('bondWeakened', { strength: this.bondStrength });
}
}
/**
* Visual bond pulse effect
*/
showBondPulse() {
// TODO: Create particle effect at player position
console.log('💫 Bond pulse visualization');
}
/**
* Ability: Telepathy (send message to Ana)
*/
useTelepathy(message) {
if (!this.abilities.telepathy.unlocked) {
console.log('❌ Telepathy not unlocked');
return false;
}
if (this.abilities.telepathy.cooldown > 0) {
console.log('⏸️ Telepathy on cooldown');
return false;
}
console.log(`📡 Sending to Ana: ${message}`);
// Ana responds after delay
this.scene.time.delayedCall(2000, () => {
const responses = [
"I heard you! Keep searching!",
"Kai... I'm trying to stay strong...",
"They don't know about our bond. Use that!",
"I can feel you getting closer!"
];
const response = Phaser.Utils.Array.GetRandom(responses);
this.showTelepathicMessage(response, 'determined');
});
// Set cooldown
this.abilities.telepathy.cooldown = this.abilities.telepathy.maxCooldown;
this.changeBondStrength(+2); // Strengthen bond
return true;
}
/**
* Ability: Sense Pulse (detect Ana's direction)
*/
useSensePulse() {
if (!this.abilities.sensePulse.unlocked) {
console.log('❌ Sense Pulse not unlocked');
return null;
}
if (this.abilities.sensePulse.cooldown > 0) {
console.log('⏸️ Sense Pulse on cooldown');
return null;
}
console.log('📍 Sensing Ana\'s location...');
// Calculate general direction
const playerX = this.scene.player?.x || 0;
const playerY = this.scene.player?.y || 0;
const angle = Phaser.Math.Angle.Between(
playerX, playerY,
this.anaState.direction.x, this.anaState.direction.y
);
const distance = this.anaState.distance;
// Show visual indicator
this.showDirectionIndicator(angle, distance);
// Set cooldown
this.abilities.sensePulse.cooldown = this.abilities.sensePulse.maxCooldown;
this.changeBondStrength(+5);
return {
angle: angle,
distance: distance,
distanceCategory: this.getDistanceCategory(distance)
};
}
/**
* Get distance category (for vague communication)
*/
getDistanceCategory(distance) {
if (distance < 500) return 'very_close';
if (distance < 1500) return 'close';
if (distance < 3000) return 'far';
return 'very_far';
}
/**
* Show direction indicator
*/
showDirectionIndicator(angle, distance) {
const category = this.getDistanceCategory(distance);
const messages = {
'very_close': 'Ana is VERY CLOSE! ⬆️',
'close': 'Ana is nearby 📍',
'far': 'Ana is far away 🔭',
'very_far': 'Ana is very far 🌌'
};
console.log(`📍 ${messages[category]} (${Math.round(distance)}px)`);
// TODO: Show UI arrow pointing in direction
}
/**
* Update Ana's danger level
*/
updateAnaDanger(deltaSeconds) {
// Danger level increases over time (captors getting desperate)
if (this.anaState.alive) {
this.anaState.dangerLevel = Math.min(
100,
this.anaState.dangerLevel + 0.1 * deltaSeconds
);
// Trigger danger events
if (this.anaState.dangerLevel > 70 && Math.random() < 0.01) {
const dangerEvent = this.bondEvents.find(e => e.id === 'danger_warning');
if (dangerEvent) {
this.showTelepathicMessage(dangerEvent.message, dangerEvent.emotion);
}
}
}
}
/**
* Update Ana's position (for story progression)
*/
updateAnaLocation(x, y, distance) {
this.anaState.direction.x = x;
this.anaState.direction.y = y;
this.anaState.distance = distance;
console.log(`📍 Ana's location updated: (${x}, ${y}), distance: ${distance}px`);
}
/**
* Create Twin Bond UI
*/
createBondUI() {
const width = this.scene.cameras.main.width;
// Bond meter (top-left)
const x = 20;
const y = 120;
// Background
const bg = this.scene.add.rectangle(x, y, 200, 40, 0x2d1b00, 0.8);
bg.setOrigin(0, 0);
bg.setScrollFactor(0);
bg.setDepth(100);
// Title
const title = this.scene.add.text(x + 10, y + 5, '💞 Twin Bond', {
fontSize: '14px',
fontFamily: 'Georgia, serif',
color: '#FFD700',
fontStyle: 'bold'
});
title.setScrollFactor(0);
title.setDepth(100);
// Bond bar
const barBg = this.scene.add.rectangle(x + 10, y + 25, 180, 8, 0x000000, 0.8);
barBg.setOrigin(0, 0);
barBg.setScrollFactor(0);
barBg.setDepth(100);
const barFill = this.scene.add.rectangle(
x + 10, y + 25,
180 * (this.bondStrength / 100),
8,
0xFF69B4,
1
);
barFill.setOrigin(0, 0);
barFill.setScrollFactor(0);
barFill.setDepth(100);
this.bondUI = { bg, title, barBg, barFill };
// Update bar every frame
this.scene.events.on('update', () => {
if (this.bondUI && this.bondUI.barFill) {
this.bondUI.barFill.width = 180 * (this.bondStrength / 100);
}
});
}
/**
* Getters
*/
getBondStrength() {
return this.bondStrength;
}
getAnaStatus() {
return this.anaState;
}
isAnaSafe() {
return this.anaState.dangerLevel < 50;
}
}

View File

@@ -0,0 +1,624 @@
/**
* VISUAL ENHANCEMENT SYSTEM
* Central system for managing visual effects, animations, and polish
*/
class VisualEnhancementSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Sub-systems
this.animatedTextures = new Map();
this.weatherEffects = [];
this.lightSources = [];
this.shadows = [];
this.particles = [];
// Settings
this.settings = {
animatedTextures: true,
weatherEffects: true,
dynamicLighting: true,
shadows: true,
fogOfWar: false,
particleQuality: 'high', // low, medium, high, ultra
animationQuality: 'high',
screenShake: true,
transitions: true
};
// Animation timers
this.waterAnimTime = 0;
this.treeAnimTime = 0;
this.fireAnimTime = 0;
this.loadSettings();
this.init();
console.log('✅ Visual Enhancement System initialized');
}
init() {
this.initAnimatedTextures();
this.initWeatherEffects();
this.initLightingSystem();
this.initShadowSystem();
this.initParticleSystem();
}
// ========== ANIMATED TEXTURES ==========
initAnimatedTextures() {
if (!this.settings.animatedTextures) return;
// Create water animation frames
this.createWaterAnimation();
// Create fire animation
this.createFireAnimation();
// Create tree leaf animation
this.createTreeAnimation();
console.log('🎬 Animated textures initialized');
}
createWaterAnimation() {
// Water flow animation (4 frames)
const frames = [];
for (let i = 0; i < 4; i++) {
const graphics = this.scene.add.graphics();
graphics.fillStyle(0x4488ff, 1);
graphics.fillRect(0, 0, 64, 64);
// Add wave pattern
graphics.lineStyle(2, 0x6699ff, 0.5);
for (let y = 0; y < 64; y += 8) {
const offset = Math.sin((y + i * 16) * 0.1) * 4;
graphics.lineBetween(0, y + offset, 64, y + offset);
}
graphics.generateTexture('water_anim_' + i, 64, 64);
graphics.destroy();
frames.push('water_anim_' + i);
}
this.animatedTextures.set('water', {
frames,
currentFrame: 0,
speed: 200 // ms per frame
});
}
createFireAnimation() {
// Fire flickering (3 frames)
const frames = [];
const colors = [0xff6600, 0xff8800, 0xffaa00];
for (let i = 0; i < 3; i++) {
const graphics = this.scene.add.graphics();
graphics.fillStyle(colors[i], 1);
graphics.fillCircle(16, 16, 12 + i * 2);
graphics.generateTexture('fire_anim_' + i, 32, 32);
graphics.destroy();
frames.push('fire_anim_' + i);
}
this.animatedTextures.set('fire', {
frames,
currentFrame: 0,
speed: 150
});
}
createTreeAnimation() {
// Tree leaf rustling (subtle movement)
console.log('🌳 Tree animation ready');
}
updateAnimatedTextures(delta) {
if (!this.settings.animatedTextures) return;
this.waterAnimTime += delta;
this.fireAnimTime += delta;
// Update water
const water = this.animatedTextures.get('water');
if (water && this.waterAnimTime > water.speed) {
water.currentFrame = (water.currentFrame + 1) % water.frames.length;
this.waterAnimTime = 0;
}
// Update fire
const fire = this.animatedTextures.get('fire');
if (fire && this.fireAnimTime > fire.speed) {
fire.currentFrame = (fire.currentFrame + 1) % fire.frames.length;
this.fireAnimTime = 0;
}
}
// ========== WEATHER EFFECTS ==========
initWeatherEffects() {
if (!this.settings.weatherEffects) return;
console.log('🌦️ Weather effects initialized');
}
createSnowEffect() {
const emitter = this.scene.add.particles(0, 0, 'particle_white', {
x: { min: 0, max: this.scene.cameras.main.width },
y: -10,
speedY: { min: 50, max: 100 },
speedX: { min: -20, max: 20 },
scale: { min: 0.3, max: 0.8 },
alpha: { min: 0.5, max: 1 },
lifespan: 10000,
frequency: 50,
quantity: 2
});
emitter.setScrollFactor(0);
this.weatherEffects.push(emitter);
return emitter;
}
createRainEffect() {
const emitter = this.scene.add.particles(0, 0, 'particle_white', {
x: { min: 0, max: this.scene.cameras.main.width },
y: -10,
speedY: { min: 400, max: 600 },
speedX: { min: -50, max: -30 },
scaleX: 0.1,
scaleY: 0.5,
alpha: 0.6,
lifespan: 2000,
frequency: 10,
quantity: 5,
tint: 0x88ccff
});
emitter.setScrollFactor(0);
this.weatherEffects.push(emitter);
return emitter;
}
createLightningFlash() {
const flash = this.scene.add.rectangle(
this.scene.cameras.main.centerX,
this.scene.cameras.main.centerY,
this.scene.cameras.main.width,
this.scene.cameras.main.height,
0xffffff,
0.8
);
flash.setScrollFactor(0);
flash.setDepth(10000);
this.scene.tweens.add({
targets: flash,
alpha: 0,
duration: 200,
onComplete: () => flash.destroy()
});
}
createFogEffect() {
// Atmospheric fog overlay - covers entire screen
const fogOverlay = this.scene.add.graphics();
fogOverlay.setScrollFactor(0);
fogOverlay.setDepth(8000); // Above game, below UI
fogOverlay.setAlpha(0.4); // Semi-transparent
// Create fog particles for movement effect
const fogParticles = this.scene.add.particles(0, 0, 'particle_white', {
x: { min: 0, max: this.scene.cameras.main.width },
y: { min: 0, max: this.scene.cameras.main.height },
speedX: { min: -10, max: 10 },
speedY: { min: -5, max: 5 },
scale: { min: 2, max: 4 },
alpha: { min: 0.1, max: 0.3 },
lifespan: 10000,
frequency: 100,
quantity: 3,
tint: 0xcccccc,
blendMode: 'SCREEN'
});
fogParticles.setScrollFactor(0);
fogParticles.setDepth(8001);
// Animated fog overlay
let fogTime = 0;
const updateFog = () => {
fogTime += 0.01;
fogOverlay.clear();
const cam = this.scene.cameras.main;
// Draw multiple layers of fog for depth
for (let layer = 0; layer < 3; layer++) {
const offset = Math.sin(fogTime + layer) * 50;
const alpha = 0.1 + layer * 0.05;
fogOverlay.fillStyle(0xffffff, alpha);
// Draw wavy fog shapes
for (let i = 0; i < 5; i++) {
const x = (i * cam.width / 4) + offset;
const y = (layer * 100) + Math.cos(fogTime + i) * 30;
fogOverlay.fillCircle(x, y, 200 + layer * 50);
}
}
};
// Update fog animation every frame
this.scene.events.on('update', updateFog);
this.weatherEffects.push({
overlay: fogOverlay,
particles: fogParticles,
update: updateFog,
destroy: () => {
fogOverlay.destroy();
fogParticles.destroy();
this.scene.events.off('update', updateFog);
}
});
console.log('🌫️ Atmospheric fog effect created');
return { overlay: fogOverlay, particles: fogParticles };
}
// ========== LIGHTING SYSTEM ==========
initLightingSystem() {
if (!this.settings.dynamicLighting) return;
this.lightingLayer = this.scene.add.layer();
this.lightingLayer.setDepth(5000);
console.log('💡 Lighting system initialized');
}
addLight(x, y, radius = 100, color = 0xffaa00, intensity = 0.6) {
if (!this.settings.dynamicLighting) return null;
// Create radial gradient light
const light = this.scene.add.graphics();
const gradient = light.createRadialGradient(
radius, radius, 0,
radius, radius, radius
);
gradient.addColorStop(0, `rgba(${(color >> 16) & 0xff}, ${(color >> 8) & 0xff}, ${color & 0xff}, ${intensity})`);
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
light.fillStyle(color, intensity);
light.fillCircle(radius, radius, radius);
light.setPosition(x - radius, y - radius);
light.setBlendMode(Phaser.BlendModes.ADD);
light.setDepth(5001);
const lightObj = {
graphics: light,
x, y, radius, color, intensity,
flickering: false
};
this.lightSources.push(lightObj);
return lightObj;
}
addTorch(x, y) {
const torch = this.addLight(x, y, 80, 0xff6600, 0.5);
if (torch) {
torch.flickering = true;
}
return torch;
}
updateLighting(delta) {
if (!this.settings.dynamicLighting) return;
// Update flickering lights
for (const light of this.lightSources) {
if (light.flickering) {
const flicker = 0.4 + Math.random() * 0.2;
light.graphics.setAlpha(flicker);
}
}
// Update ambient lighting based on time of day
if (this.scene.weatherSystem) {
const time = this.scene.weatherSystem.gameTime;
const isNight = time < 6 || time > 18;
if (isNight) {
// Darker at night
this.scene.cameras.main.setAlpha(0.7);
} else {
this.scene.cameras.main.setAlpha(1.0);
}
}
}
// ========== SHADOW SYSTEM ==========
initShadowSystem() {
if (!this.settings.shadows) return;
console.log('🌑 Shadow system initialized');
}
addShadow(entity, offsetX = 0, offsetY = 10, width = 40, height = 15) {
if (!this.settings.shadows) return null;
const shadow = this.scene.add.ellipse(
entity.x + offsetX,
entity.y + offsetY,
width,
height,
0x000000,
0.3
);
shadow.setDepth(0);
const shadowObj = { entity, shadow, offsetX, offsetY };
this.shadows.push(shadowObj);
return shadow;
}
updateShadows() {
if (!this.settings.shadows) return;
// Get time of day for shadow opacity
let opacity = 0.3;
if (this.scene.weatherSystem) {
const time = this.scene.weatherSystem.gameTime;
// Darker shadows at noon, lighter at dawn/dusk
opacity = 0.2 + Math.abs(Math.sin((time / 24) * Math.PI * 2)) * 0.3;
}
// Update shadow positions
for (const { entity, shadow, offsetX, offsetY } of this.shadows) {
if (entity.sprite) {
shadow.x = entity.sprite.x + offsetX;
shadow.y = entity.sprite.y + offsetY;
shadow.setAlpha(opacity);
}
}
}
// ========== PARTICLE SYSTEM ==========
initParticleSystem() {
// Create particle textures
this.createParticleTextures();
console.log('✨ Particle system initialized');
}
createParticleTextures() {
// White particle
const white = this.scene.add.graphics();
white.fillStyle(0xffffff, 1);
white.fillCircle(4, 4, 4);
white.generateTexture('particle_white', 8, 8);
white.destroy();
// Sparkle
const sparkle = this.scene.add.graphics();
sparkle.fillStyle(0xffff00, 1);
sparkle.fillCircle(3, 3, 3);
sparkle.generateTexture('particle_sparkle', 6, 6);
sparkle.destroy();
// Heart
const heart = this.scene.add.graphics();
heart.fillStyle(0xff0066, 1);
heart.fillCircle(3, 3, 3);
heart.fillCircle(5, 3, 3);
heart.fillTriangle(1, 4, 7, 4, 4, 8);
heart.generateTexture('particle_heart', 8, 8);
heart.destroy();
}
createHeartParticles(x, y) {
const emitter = this.scene.add.particles(x, y, 'particle_heart', {
speed: { min: 20, max: 50 },
angle: { min: -120, max: -60 },
scale: { start: 1, end: 0 },
alpha: { start: 1, end: 0 },
lifespan: 1000,
quantity: 5,
blendMode: 'ADD'
});
this.scene.time.delayedCall(1000, () => emitter.destroy());
return emitter;
}
createSparkleEffect(x, y) {
const emitter = this.scene.add.particles(x, y, 'particle_sparkle', {
speed: { min: 10, max: 30 },
scale: { start: 1, end: 0 },
alpha: { start: 1, end: 0 },
lifespan: 800,
quantity: 10,
blendMode: 'ADD'
});
this.scene.time.delayedCall(800, () => emitter.destroy());
return emitter;
}
createCheckmarkEffect(x, y) {
const checkmark = this.scene.add.text(x, y, '✓', {
fontSize: '32px',
color: '#00ff00',
fontStyle: 'bold'
});
checkmark.setOrigin(0.5);
checkmark.setDepth(10000);
this.scene.tweens.add({
targets: checkmark,
y: y - 50,
alpha: 0,
scale: 2,
duration: 1000,
ease: 'Power2',
onComplete: () => checkmark.destroy()
});
}
// ========== SCREEN EFFECTS ==========
screenShake(intensity = 10, duration = 300) {
if (!this.settings.screenShake) return;
this.scene.cameras.main.shake(duration, intensity / 1000);
}
screenFlash(color = 0xffffff, duration = 200) {
this.scene.cameras.main.flash(duration,
(color >> 16) & 0xff,
(color >> 8) & 0xff,
color & 0xff
);
}
fadeOut(duration = 500, callback) {
if (!this.settings.transitions) {
if (callback) callback();
return;
}
this.scene.cameras.main.fadeOut(duration, 0, 0, 0);
if (callback) {
this.scene.cameras.main.once('camerafadeoutcomplete', callback);
}
}
fadeIn(duration = 500) {
if (!this.settings.transitions) return;
this.scene.cameras.main.fadeIn(duration, 0, 0, 0);
}
// ========== BUILDING EFFECTS ==========
createConstructionEffect(x, y) {
// Dust particles during construction
const emitter = this.scene.add.particles(x, y, 'particle_white', {
speed: { min: 20, max: 40 },
scale: { start: 0.5, end: 0 },
alpha: { start: 0.5, end: 0 },
lifespan: 1000,
quantity: 3,
frequency: 100,
tint: 0x996633
});
return emitter;
}
createSmokeEffect(x, y) {
// Chimney smoke
const emitter = this.scene.add.particles(x, y, 'particle_white', {
speedY: { min: -30, max: -50 },
speedX: { min: -10, max: 10 },
scale: { start: 0.3, end: 1 },
alpha: { start: 0.5, end: 0 },
lifespan: 2000,
frequency: 500,
quantity: 1,
tint: 0x888888
});
return emitter;
}
// ========== FARM AUTOMATION VISUALS ==========
createPowerGridEffect(x1, y1, x2, y2) {
// Electric arc between power sources
const graphics = this.scene.add.graphics();
graphics.lineStyle(2, 0x00ffff, 0.8);
// Draw lightning-like connection
const steps = 5;
const points = [];
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const x = x1 + (x2 - x1) * t + (Math.random() - 0.5) * 10;
const y = y1 + (y2 - y1) * t + (Math.random() - 0.5) * 10;
points.push({ x, y });
}
graphics.beginPath();
graphics.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
graphics.lineTo(points[i].x, points[i].y);
}
graphics.strokePath();
// Fade out
this.scene.tweens.add({
targets: graphics,
alpha: 0,
duration: 200,
onComplete: () => graphics.destroy()
});
}
createMutantGlow(entity, color = 0x00ff00) {
// Radioactive glow for mutants
const glow = this.scene.add.circle(
entity.x,
entity.y,
30,
color,
0.3
);
glow.setBlendMode(Phaser.BlendModes.ADD);
glow.setDepth(entity.depth - 1);
// Pulsing animation
this.scene.tweens.add({
targets: glow,
scale: { from: 1, to: 1.2 },
alpha: { from: 0.3, to: 0.1 },
duration: 1000,
yoyo: true,
repeat: -1
});
return glow;
}
// ========== UPDATE ==========
update(delta) {
this.updateAnimatedTextures(delta);
this.updateLighting(delta);
this.updateShadows();
}
// ========== SETTINGS ==========
saveSettings() {
localStorage.setItem('novafarma_visual_enhancements', JSON.stringify(this.settings));
}
loadSettings() {
const saved = localStorage.getItem('novafarma_visual_enhancements');
if (saved) {
this.settings = { ...this.settings, ...JSON.parse(saved) };
}
}
destroy() {
if (this.lightingLayer) this.lightingLayer.destroy();
for (const { shadow } of this.shadows) {
shadow.destroy();
}
for (const effect of this.weatherEffects) {
effect.destroy();
}
console.log('✨ Visual Enhancement System destroyed');
}
}

View File

@@ -0,0 +1,444 @@
/**
* WORKER CREATURES SYSTEM
* Specialized creature workers with unique abilities
*/
class WorkerCreaturesSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Worker creatures
this.workers = new Map();
// Creature types
this.creatureTypes = new Map();
// Active tasks
this.activeTasks = [];
this.init();
console.log('✅ Worker Creatures System initialized');
}
init() {
this.defineCreatureTypes();
console.log('🦌 Worker creatures ready');
}
// ========== CREATURE TYPES ==========
defineCreatureTypes() {
// Donkey - Transport specialist
this.defineCreature('donkey', {
name: 'Donkey',
specialty: 'transport',
efficiency: 0.8,
abilities: ['carry_items', 'cart_transport', 'long_distance'],
carryCapacity: 50,
speed: 1.2,
tamingDifficulty: 'easy',
cost: { carrot: 10, apple: 5 }
});
// Bigfoot - Forest gathering specialist
this.defineCreature('bigfoot', {
name: 'Bigfoot',
specialty: 'gathering',
efficiency: 1.0,
abilities: ['tree_chopping', 'berry_picking', 'mushroom_finding', 'forest_navigation'],
gatherBonus: 1.5,
speed: 0.9,
tamingDifficulty: 'medium',
cost: { honey: 5, berries: 20 }
});
// Yeti - Snow biome specialist
this.defineCreature('yeti', {
name: 'Yeti',
specialty: 'snow_tasks',
efficiency: 1.2,
abilities: ['ice_mining', 'snow_clearing', 'cold_resistance', 'ice_fishing'],
coldBonus: 2.0,
speed: 0.8,
tamingDifficulty: 'hard',
cost: { frozen_meat: 10, ice_crystal: 3 }
});
// Elf - Crafting specialist
this.defineCreature('elf', {
name: 'Elf',
specialty: 'crafting',
efficiency: 1.5,
abilities: ['auto_craft', 'enchanting', 'potion_making', 'tool_repair'],
craftingSpeed: 2.0,
speed: 1.0,
tamingDifficulty: 'hard',
cost: { magic_dust: 5, golden_apple: 2 }
});
// Gnome - Mining specialist
this.defineCreature('gnome', {
name: 'Gnome',
specialty: 'mining',
efficiency: 1.3,
abilities: ['ore_detection', 'tunnel_digging', 'gem_finding', 'cave_navigation'],
miningBonus: 1.8,
speed: 0.7,
tamingDifficulty: 'medium',
cost: { gold_nugget: 5, diamond: 1 }
});
// Fairy - Plant care specialist
this.defineCreature('fairy', {
name: 'Fairy',
specialty: 'plant_care',
efficiency: 1.4,
abilities: ['instant_growth', 'disease_cure', 'crop_blessing', 'flower_magic'],
growthBonus: 2.5,
speed: 1.5,
tamingDifficulty: 'very_hard',
cost: { rainbow_flower: 3, fairy_dust: 10 }
});
// Golem - Heavy labor specialist
this.defineCreature('golem', {
name: 'Golem',
specialty: 'heavy_labor',
efficiency: 0.9,
abilities: ['boulder_moving', 'building_construction', 'land_clearing', 'defense'],
strengthBonus: 3.0,
speed: 0.5,
tamingDifficulty: 'very_hard',
cost: { stone: 100, iron: 50, magic_core: 1 }
});
// Dragon - Ultimate worker
this.defineCreature('dragon', {
name: 'Dragon',
specialty: 'all',
efficiency: 2.0,
abilities: ['flight', 'fire_breath', 'treasure_finding', 'all_tasks'],
allBonus: 2.0,
speed: 2.0,
tamingDifficulty: 'legendary',
cost: { dragon_egg: 1, legendary_meat: 10, gold: 1000 }
});
}
defineCreature(id, data) {
this.creatureTypes.set(id, {
id,
...data
});
}
// ========== TAMING ==========
canTame(creatureType) {
const creature = this.creatureTypes.get(creatureType);
if (!creature) return false;
// Check if player has required items
if (!this.scene.inventorySystem) return false;
for (const [item, amount] of Object.entries(creature.cost)) {
const has = this.scene.inventorySystem.getItemCount(item);
if (has < amount) {
console.log(`❌ Missing ${item}: ${has}/${amount}`);
return false;
}
}
return true;
}
tameCreature(creatureType, x, y) {
if (!this.canTame(creatureType)) return false;
const creatureData = this.creatureTypes.get(creatureType);
// Consume taming items
for (const [item, amount] of Object.entries(creatureData.cost)) {
this.scene.inventorySystem.removeItem(item, amount);
}
// Create worker
const worker = {
id: `worker_${creatureType}_${Date.now()}`,
type: creatureType,
name: creatureData.name,
specialty: creatureData.specialty,
efficiency: creatureData.efficiency,
abilities: creatureData.abilities,
x, y,
currentTask: null,
level: 1,
xp: 0,
loyalty: 50,
sprite: null
};
this.workers.set(worker.id, worker);
// Notify automation tier system
if (this.scene.automationTiers) {
this.scene.automationTiers.befriendCreature();
}
// Visual effect
if (this.scene.visualEnhancements) {
this.scene.visualEnhancements.createSparkleEffect(x, y);
this.scene.visualEnhancements.screenFlash(0x00ff00, 500);
}
console.log(`✅ Tamed ${creatureData.name}!`);
return worker;
}
// ========== TASK ASSIGNMENT ==========
assignTask(workerId, task) {
const worker = this.workers.get(workerId);
if (!worker) return false;
const creatureData = this.creatureTypes.get(worker.type);
// Check if creature can do this task
if (!this.canDoTask(worker, task)) {
console.log(`${worker.name} cannot do ${task.type}`);
return false;
}
// Assign task
worker.currentTask = {
...task,
startTime: Date.now(),
efficiency: this.calculateEfficiency(worker, task)
};
console.log(`📋 ${worker.name} assigned to ${task.type}`);
return true;
}
canDoTask(worker, task) {
const creatureData = this.creatureTypes.get(worker.type);
// Dragons can do everything
if (worker.type === 'dragon') return true;
// Check specialty
const taskSpecialties = {
'transport': ['donkey'],
'gather_wood': ['bigfoot'],
'gather_berries': ['bigfoot'],
'mine_ice': ['yeti'],
'ice_fishing': ['yeti'],
'craft_item': ['elf'],
'enchant_item': ['elf'],
'mine_ore': ['gnome'],
'find_gems': ['gnome'],
'water_crops': ['fairy'],
'grow_crops': ['fairy'],
'build': ['golem'],
'clear_land': ['golem']
};
const validTypes = taskSpecialties[task.type] || [];
return validTypes.includes(worker.type);
}
calculateEfficiency(worker, task) {
const creatureData = this.creatureTypes.get(worker.type);
let efficiency = creatureData.efficiency;
// Apply specialty bonuses
if (task.type.includes('gather') && creatureData.gatherBonus) {
efficiency *= creatureData.gatherBonus;
}
if (task.type.includes('mine') && creatureData.miningBonus) {
efficiency *= creatureData.miningBonus;
}
if (task.type.includes('craft') && creatureData.craftingSpeed) {
efficiency *= creatureData.craftingSpeed;
}
if (task.type.includes('grow') && creatureData.growthBonus) {
efficiency *= creatureData.growthBonus;
}
// Apply level bonus
efficiency *= (1 + worker.level * 0.1);
return efficiency;
}
// ========== TASK EXECUTION ==========
updateWorkers(delta) {
for (const worker of this.workers.values()) {
if (!worker.currentTask) continue;
const elapsed = Date.now() - worker.currentTask.startTime;
const taskTime = worker.currentTask.duration / worker.currentTask.efficiency;
if (elapsed >= taskTime) {
this.completeTask(worker);
}
}
}
completeTask(worker) {
const task = worker.currentTask;
// Execute task
this.executeTask(worker, task);
// Grant XP
this.addWorkerXP(worker, task.xp || 10);
// Increase loyalty
worker.loyalty = Math.min(100, worker.loyalty + 1);
// Clear task
worker.currentTask = null;
console.log(`${worker.name} completed ${task.type}!`);
}
executeTask(worker, task) {
switch (task.type) {
case 'transport':
this.transportItems(worker, task);
break;
case 'gather_wood':
this.gatherResource(worker, 'wood', task.amount);
break;
case 'gather_berries':
this.gatherResource(worker, 'berries', task.amount);
break;
case 'mine_ore':
this.gatherResource(worker, 'ore', task.amount);
break;
case 'craft_item':
this.craftItem(worker, task.item);
break;
case 'grow_crops':
this.growCrops(worker, task.crops);
break;
default:
console.log(`Unknown task: ${task.type}`);
}
}
transportItems(worker, task) {
// Move items from A to B
console.log(`🚚 ${worker.name} transported items`);
}
gatherResource(worker, resource, amount) {
// Add resource to inventory
if (this.scene.inventorySystem) {
const bonus = this.workers.get(worker.id).currentTask.efficiency;
const finalAmount = Math.floor(amount * bonus);
this.scene.inventorySystem.addItem(resource, finalAmount);
console.log(`📦 Gathered ${finalAmount} ${resource}`);
}
}
craftItem(worker, item) {
// Craft item
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addItem(item, 1);
console.log(`🔨 Crafted ${item}`);
}
}
growCrops(worker, crops) {
// Instantly grow crops
console.log(`🌱 Grew ${crops.length} crops`);
}
// ========== LEVELING ==========
addWorkerXP(worker, amount) {
worker.xp += amount;
const xpNeeded = this.getXPForLevel(worker.level + 1);
if (worker.xp >= xpNeeded) {
this.levelUpWorker(worker);
}
}
getXPForLevel(level) {
return Math.floor(100 * Math.pow(1.5, level - 1));
}
levelUpWorker(worker) {
worker.level++;
worker.xp = 0;
console.log(`🎉 ${worker.name} leveled up to ${worker.level}!`);
// Visual effect
if (this.scene.visualEnhancements) {
this.scene.visualEnhancements.createSparkleEffect(worker.x, worker.y);
}
}
// ========== SPECIAL ABILITIES ==========
useAbility(workerId, abilityName) {
const worker = this.workers.get(workerId);
if (!worker) return false;
const creatureData = this.creatureTypes.get(worker.type);
if (!creatureData.abilities.includes(abilityName)) {
console.log(`${worker.name} doesn't have ${abilityName}`);
return false;
}
// Execute ability
switch (abilityName) {
case 'instant_growth':
this.instantGrowth(worker);
break;
case 'fire_breath':
this.fireBreath(worker);
break;
case 'treasure_finding':
this.findTreasure(worker);
break;
default:
console.log(`${worker.name} used ${abilityName}!`);
}
return true;
}
instantGrowth(worker) {
// Fairy ability - instantly grow all nearby crops
console.log('🌸 Fairy magic! All crops instantly grown!');
}
fireBreath(worker) {
// Dragon ability - clear area with fire
console.log('🔥 Dragon fire breath!');
}
findTreasure(worker) {
// Dragon ability - find hidden treasure
console.log('💎 Found treasure!');
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addItem('gold', 100);
}
}
// ========== UPDATE ==========
update(delta) {
this.updateWorkers(delta);
}
destroy() {
console.log('🦌 Worker Creatures System destroyed');
}
}

View File

@@ -0,0 +1,239 @@
/**
* ZOMBIE WORKER AI SYSTEM
* Tamed zombies lahko opravljajo delo (farming, mining)
*/
class ZombieWorkerSystem {
constructor(scene) {
this.scene = scene;
this.workers = [];
}
assignWork(zombie, workType, workRadius = 5) {
if (!zombie.isTamed) {
console.warn('⚠️ Cannot assign work to untamed zombie!');
return false;
}
zombie.workerData = {
type: workType,
radius: workRadius,
centerX: zombie.gridX,
centerY: zombie.gridY,
energy: 100,
decayRate: 0.5,
workTimer: 0,
workInterval: 5000,
status: 'IDLE',
inventory: {
seeds: 0,
hoe: 0,
watering_can: 0,
pickaxe: 0
}
};
this.workers.push(zombie);
console.log(`🧟 Assigned ${zombie.type} as ${workType} worker`);
return true;
}
removeWorker(zombie) {
const index = this.workers.indexOf(zombie);
if (index > -1) {
this.workers.splice(index, 1);
zombie.workerData = null;
}
}
update(delta) {
this.workers = this.workers.filter(w =>
this.scene.npcs.includes(w) && w.hp > 0
);
for (const worker of this.workers) {
if (!worker.workerData) continue;
this.applyDecay(worker, delta);
worker.workerData.workTimer += delta;
if (worker.workerData.workTimer >= worker.workerData.workInterval) {
worker.workerData.workTimer = 0;
if (worker.workerData.type === 'FARM') {
this.performFarmWork(worker);
} else if (worker.workerData.type === 'MINE') {
this.performMineWork(worker);
} else if (worker.workerData.type === 'CLEAR') {
this.performClearWork(worker);
}
}
}
}
applyDecay(zombie, delta) {
const wd = zombie.workerData;
wd.energy -= (wd.decayRate * delta) / 1000;
if (wd.energy <= 0) {
wd.energy = 0;
zombie.hp -= (wd.decayRate * delta) / 1000;
if (zombie.sprite) zombie.sprite.setTint(0x666666);
}
if (zombie.hp <= 0) this.onWorkerDeath(zombie);
}
performFarmWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
const farming = this.scene.farmingSystem;
if (!terrain || !farming) return;
// 1. Water/Harvest existing crops
if (terrain.cropsMap) {
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.cropsMap.has(key)) {
const crop = terrain.cropsMap.get(key);
if (!crop.isWatered) {
farming.waterCrop(wd.centerX + dx, wd.centerY + dy);
console.log(`🧟💧 Worker watered`);
wd.status = 'WORKING';
return;
}
if (crop.stage >= 4) {
farming.harvest(wd.centerX + dx, wd.centerY + dy);
console.log(`🧟🌾 Worker harvested`);
wd.status = 'WORKING';
return;
}
}
}
}
}
// 2. Plant on empty tilled soil (if has seeds)
if (wd.inventory.seeds > 0) {
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
if (tile && tile.isTilled && !tile.hasCrop) {
farming.plant(wd.centerX + dx, wd.centerY + dy, 'wheat');
wd.inventory.seeds--;
console.log(`🧟🌱 Worker planted (Seeds: ${wd.inventory.seeds})`);
wd.status = 'WORKING';
return;
}
}
}
}
// 3. Till grass/dirt
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const tile = terrain.getTile(wd.centerX + dx, wd.centerY + dy);
const typeName = tile?.type?.name || tile?.type;
if ((typeName === 'grass' || typeName === 'dirt') && !tile.isTilled) {
tile.isTilled = true;
if (tile.sprite) tile.sprite.setTint(0x8B4513);
console.log(`🧟🔨 Worker tilled soil`);
wd.status = 'WORKING';
return;
}
}
}
wd.status = 'IDLE';
}
performMineWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
if (!terrain || !terrain.decorationsMap) return;
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
if (decor.type === 'stone' || decor.type.includes('rock')) {
terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
if (this.scene.inventorySystem) {
this.scene.inventorySystem.addItem('stone', 1);
}
console.log(`🧟⛏️ Worker mined stone`);
wd.status = 'WORKING';
return;
}
}
}
}
wd.status = 'IDLE';
}
performClearWork(zombie) {
const wd = zombie.workerData;
const terrain = this.scene.terrainSystem;
if (!terrain || !terrain.decorationsMap) return;
for (let dx = -wd.radius; dx <= wd.radius; dx++) {
for (let dy = -wd.radius; dy <= wd.radius; dy++) {
const key = `${wd.centerX + dx},${wd.centerY + dy}`;
if (terrain.decorationsMap.has(key)) {
const decor = terrain.decorationsMap.get(key);
const t = decor.type;
// Clear trees, bushes, logs, rocks
if (t.startsWith('tree') || t.startsWith('bush') || t === 'fallen_log' || t === 'stone' || t.startsWith('small_rock')) {
terrain.removeDecoration(wd.centerX + dx, wd.centerY + dy);
// Give some resources
if (this.scene.inventorySystem) {
if (t.startsWith('tree') || t === 'fallen_log' || t.startsWith('bush')) {
this.scene.inventorySystem.addItem('wood', 1);
} else {
this.scene.inventorySystem.addItem('stone', 1);
}
}
console.log(`🧟🪓 Worker CLEARED ${t}`);
wd.status = 'WORKING';
return; // One per tick
}
}
}
}
wd.status = 'IDLE';
}
onWorkerDeath(zombie) {
console.log(`💀 Worker died at ${zombie.gridX},${zombie.gridY}`);
if (this.scene.interactionSystem && this.scene.interactionSystem.spawnLoot) {
this.scene.interactionSystem.spawnLoot(zombie.gridX, zombie.gridY, 'fertilizer', 1);
}
if (this.scene.statsSystem) {
this.scene.statsSystem.addXP(10);
}
this.removeWorker(zombie);
}
feedWorker(zombie, amount = 50) {
if (!zombie.workerData) return false;
zombie.workerData.energy = Math.min(100, zombie.workerData.energy + amount);
if (zombie.sprite) zombie.sprite.clearTint();
console.log(`🍖 Fed worker`);
return true;
}
}