ok
This commit is contained in:
516
MASTER_DESIGN_RECOVERY/DialogueSystem.js
Normal file
516
MASTER_DESIGN_RECOVERY/DialogueSystem.js
Normal 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;
|
||||
}
|
||||
}
|
||||
371
MASTER_DESIGN_RECOVERY/MainQuestAnaSystem.js
Normal file
371
MASTER_DESIGN_RECOVERY/MainQuestAnaSystem.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
446
MASTER_DESIGN_RECOVERY/MasterGameSystemsManager.js
Normal file
446
MASTER_DESIGN_RECOVERY/MasterGameSystemsManager.js
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
506
MASTER_DESIGN_RECOVERY/StoryQuestSystem.js
Normal file
506
MASTER_DESIGN_RECOVERY/StoryQuestSystem.js
Normal 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');
|
||||
}
|
||||
}
|
||||
403
MASTER_DESIGN_RECOVERY/TownRestorationSystem.js
Normal file
403
MASTER_DESIGN_RECOVERY/TownRestorationSystem.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
432
MASTER_DESIGN_RECOVERY/TwinBondSystem.js
Normal file
432
MASTER_DESIGN_RECOVERY/TwinBondSystem.js
Normal 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;
|
||||
}
|
||||
}
|
||||
624
MASTER_DESIGN_RECOVERY/VisualEnhancementSystem.js
Normal file
624
MASTER_DESIGN_RECOVERY/VisualEnhancementSystem.js
Normal 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');
|
||||
}
|
||||
}
|
||||
444
MASTER_DESIGN_RECOVERY/WorkerCreaturesSystem.js
Normal file
444
MASTER_DESIGN_RECOVERY/WorkerCreaturesSystem.js
Normal 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');
|
||||
}
|
||||
}
|
||||
239
MASTER_DESIGN_RECOVERY/ZombieWorkerSystem.js
Normal file
239
MASTER_DESIGN_RECOVERY/ZombieWorkerSystem.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user