LEGENDARY NIGHT! P22-P25 complete - 4 new systems (2,300 LOC) - Smart Zombies, Tools, Blueprints, Ana Clues!
This commit is contained in:
413
src/systems/AnaClueSystem.js
Normal file
413
src/systems/AnaClueSystem.js
Normal file
@@ -0,0 +1,413 @@
|
||||
/**
|
||||
* ANA'S CLUE SYSTEM
|
||||
* Manages the 50 collectible clues left by Ana across the world.
|
||||
*
|
||||
* Features:
|
||||
* - 50 Collectibles: 15 Messages, 12 Photos, 23 Personal Items
|
||||
* - Story Integration: Each clue reveals plot and lore
|
||||
* - Emotional Cutscenes: Kai's reactions and Twin Bond activations
|
||||
* - Progressive Discovery: Clues unlock as player explores biomes
|
||||
* - Collection UI: Track progress, view discovered clues
|
||||
*/
|
||||
class AnaClueSystem {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
|
||||
// Clue categories
|
||||
this.categories = {
|
||||
messages: [], // 15 handwritten messages
|
||||
photos: [], // 12 photographs
|
||||
items: [] // 23 personal items
|
||||
};
|
||||
|
||||
// All 50 clues
|
||||
this.allClues = this.initializeClues();
|
||||
|
||||
// Discovered clues
|
||||
this.discovered = new Set();
|
||||
|
||||
// Story progression
|
||||
this.storyUnlocks = {
|
||||
5: 'act1_context', // First 5 clues unlock Act 1 context
|
||||
10: 'zmaj_volk_reveal', // Giant Troll King reveal
|
||||
15: 'twin_bond_boost', // Twin Bond strengthens
|
||||
25: 'act2_context', // Act 2 context unlocked
|
||||
35: 'final_location_hint', // Hint to Ana's location
|
||||
50: 'true_ending_unlock' // True ending unlocked
|
||||
};
|
||||
|
||||
// Clue locations (biome-specific)
|
||||
this.clueLocations = new Map();
|
||||
|
||||
console.log('💜 Ana\'s Clue System initialized!');
|
||||
console.log(`📊 Total Clues: ${this.allClues.length}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all 50 clues
|
||||
*/
|
||||
initializeClues() {
|
||||
const clues = [];
|
||||
|
||||
// 15 MESSAGES
|
||||
const messages = [
|
||||
{ id: 'msg_01', type: 'message', title: 'First Note', biome: 'forest', content: 'Kai, if you find this... I\'m sorry. They took me. Stay safe. -Ana' },
|
||||
{ id: 'msg_02', type: 'message', title: 'Hidden Trail', biome: 'wasteland', content: 'Following them west. Trail of red flowers marks my path.' },
|
||||
{ id: 'msg_03', type: 'message', title: 'The Creatures', biome: 'swamp', content: 'The zombies... they\'re not mindless. I saw intelligence in their eyes.' },
|
||||
{ id: 'msg_04', type: 'message', title: 'The King', biome: 'mountains', content: 'The Giant Troll King. That\'s who leads them. I heard his name.' },
|
||||
{ id: 'msg_05', type: 'message', title: 'Hope Valley', biome: 'hope_valley', content: 'Hope Valley was beautiful once. We could rebuild it together.' },
|
||||
{ id: 'msg_06', type: 'message', title: 'Twin Bond', biome: 'crystal_caves', content: 'I can feel you searching for me. Our bond is strong, Kai.' },
|
||||
{ id: 'msg_07', type: 'message', title: 'The Portals', biome: 'portal_ruins', content: 'Ancient portals everywhere. If we repair them, we could travel instantly.' },
|
||||
{ id: 'msg_08', type: 'message', title: 'Laboratory Notes', biome: 'laboratory', content: 'Found a lab. Research on "domestication" of the infected. Disturbing.' },
|
||||
{ id: 'msg_09', type: 'message', title: 'The Cure', biome: 'medical_facility', content: 'There might be a cure. Research was close before the fall.' },
|
||||
{ id: 'msg_10', type: 'message', title: 'Family Dreams', biome: 'abandoned_house', content: 'I dream of a family, Kai. Children playing in safe fields.' },
|
||||
{ id: 'msg_11', type: 'message', title: 'The Atlanteans', biome: 'atlantis', content: 'Underwater ruins! Advanced civilization. Atlantis was REAL!' },
|
||||
{ id: 'msg_12', type: 'message', title: 'Chernobyl Warning', biome: 'chernobyl', content: 'Radiation zone ahead. Be careful if you follow me here.' },
|
||||
{ id: 'msg_13', type: 'message', title: 'Amazon Expedition', biome: 'amazon', content: 'The jungle is dense but alive. So much biodiversity!' },
|
||||
{ id: 'msg_14', type: 'message', title: 'Final Warning', biome: 'dark_fortress', content: 'Don\'t come for me, Kai. Too dangerous. Save yourself!' },
|
||||
{ id: 'msg_15', type: 'message', title: 'True Feelings', biome: 'final_castle', content: 'I know you won\'t listen. You never do. I love you, brother.' }
|
||||
];
|
||||
|
||||
// 12 PHOTOS
|
||||
const photos = [
|
||||
{ id: 'photo_01', type: 'photo', title: 'Family Portrait', biome: 'forest', description: 'Photo of Kai and Ana as children, smiling.' },
|
||||
{ id: 'photo_02', type: 'photo', title: 'Attack Night', biome: 'wasteland', description: 'Blurry photo of Giant Troll King during the attack.' },
|
||||
{ id: 'photo_03', type: 'photo', title: 'Hope Valley (Before)', biome: 'hope_valley', description: 'Beautiful town before the apocalypse.' },
|
||||
{ id: 'photo_04', type: 'photo', title: 'First Zombie', biome: 'swamp', description: 'Photo of a Level 1 zombie, looking confused.' },
|
||||
{ id: 'photo_05', type: 'photo', title: 'Portal Network Map', biome: 'portal_ruins', description: 'Hand-drawn map showing portal locations.' },
|
||||
{ id: 'photo_06', type: 'photo', title: 'Laboratory Interior', biome: 'laboratory', description: 'High-tech lab equipment, still functional.' },
|
||||
{ id: 'photo_07', type: 'photo', title: 'Ana\'s Journal', biome: 'mountains', description: 'Close-up of Ana\'s handwriting.' },
|
||||
{ id: 'photo_08', type: 'photo', title: 'Atlantean Artifact', biome: 'atlantis', description: 'Glowing crystal technology from Atlantis.' },
|
||||
{ id: 'photo_09', type: 'photo', title: 'Reactor Core', biome: 'chernobyl', description: 'Damaged reactor, eerie green glow.' },
|
||||
{ id: 'photo_10', type: 'photo', title: 'Jungle Temple', biome: 'amazon', description: 'Ancient temple overgrown with vines.' },
|
||||
{ id: 'photo_11', type: 'photo', title: 'The Captor', biome: 'dark_fortress', description: 'Shadowy figure—Ana\'s captor.' },
|
||||
{ id: 'photo_12', type: 'photo', title: 'Final Message', biome: 'final_castle', description: 'Ana holding a "Find Me" sign, tears in eyes.' }
|
||||
];
|
||||
|
||||
// 23 PERSONAL ITEMS
|
||||
const items = [
|
||||
{ id: 'item_01', type: 'item', title: 'Ana\'s Bracelet', biome: 'forest', description: 'Silver bracelet with twin symbols.' },
|
||||
{ id: 'item_02', type: 'item', title: 'Torn Dress Piece', biome: 'wasteland', description: 'Fragment of Ana\'s favorite dress.' },
|
||||
{ id: 'item_03', type: 'item', title: 'Hairpin', biome: 'swamp', description: 'Ana\'s decorative hairpin, muddy but intact.' },
|
||||
{ id: 'item_04', type: 'item', title: 'Pocket Watch', biome: 'mountains', description: 'Father\'s pocket watch, Ana always carried it.' },
|
||||
{ id: 'item_05', type: 'item', title: 'Compass', biome: 'hope_valley', description: 'Engraved compass: "To Ana, find your way home."' },
|
||||
{ id: 'item_06', type: 'item', title: 'Childhood Toy', biome: 'abandoned_house', description: 'Small stuffed rabbit from childhood.' },
|
||||
{ id: 'item_07', type: 'item', title: 'Music Box', biome: 'crystal_caves', description: 'Delicate music box, plays Ana\'s favorite song.' },
|
||||
{ id: 'item_08', type: 'item', title: 'Sketchbook', biome: 'portal_ruins', description: 'Ana\'s art sketchbook, filled with drawings.' },
|
||||
{ id: 'item_09', type: 'item', title: 'Necklace', biome: 'laboratory', description: 'Mother\'s necklace, Ana\'s most treasured item.' },
|
||||
{ id: 'item_10', type: 'item', title: 'Diary Page', biome: 'medical_facility', description: 'Torn diary page describing her fears.' },
|
||||
{ id: 'item_11', type: 'item', title: 'Herb Pouch', biome: 'forest', description: 'Pouch of medicinal herbs Ana collected.' },
|
||||
{ id: 'item_12', type: 'item', title: 'Glove (Left)', biome: 'wasteland', description: 'One of Ana\'s gloves, left behind.' },
|
||||
{ id: 'item_13', type: 'item', title: 'Glove (Right)', biome: 'dark_fortress', description: 'The matching right glove.' },
|
||||
{ id: 'item_14', type: 'item', title: 'Lucky Coin', biome: 'atlantis', description: 'Old coin Ana considered lucky.' },
|
||||
{ id: 'item_15', type: 'item', title: 'Ribbon', biome: 'chernobyl', description: 'Purple ribbon from Ana\'s hair.' },
|
||||
{ id: 'item_16', type: 'item', title: 'Boot Buckle', biome: 'amazon', description: 'Buckle from Ana\'s boot, broken off.' },
|
||||
{ id: 'item_17', type: 'item', title: 'Locket', biome: 'mountains', description: 'Heart-shaped locket with Kai\'s photo inside.' },
|
||||
{ id: 'item_18', type: 'item', title: 'Handkerchief', biome: 'swamp', description: 'Embroidered handkerchief, Ana\'s initials.' },
|
||||
{ id: 'item_19', type: 'item', title: 'Map Fragment', biome: 'portal_ruins', description: 'Piece of a larger map, Ana\'s notes.' },
|
||||
{ id: 'item_20', type: 'item', title: 'Flower Press', biome: 'hope_valley', description: 'Ana pressed flowers in this book.' },
|
||||
{ id: 'item_21', type: 'item', title: 'Candle Stub', biome: 'crystal_caves', description: 'Half-burned candle, Ana used for light.' },
|
||||
{ id: 'item_22', type: 'item', title: 'Broken Mirror', biome: 'laboratory', description: 'Shattered hand mirror, Ana\'s reflection.' },
|
||||
{ id: 'item_23', type: 'item', title: 'Final Letter', biome: 'final_castle', description: 'Sealed letter: "Open only when you find me."' }
|
||||
];
|
||||
|
||||
// Combine all clues
|
||||
clues.push(...messages, ...photos, ...items);
|
||||
|
||||
// Store by category
|
||||
this.categories.messages = messages;
|
||||
this.categories.photos = photos;
|
||||
this.categories.items = items;
|
||||
|
||||
return clues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place clues in the world
|
||||
*/
|
||||
placeCluesInWorld() {
|
||||
this.allClues.forEach(clue => {
|
||||
// Determine position based on biome (placeholder logic)
|
||||
const position = this.getBiomePosition(clue.biome);
|
||||
this.clueLocations.set(clue.id, position);
|
||||
|
||||
// Spawn clue object in world (visual representation)
|
||||
if (this.scene.interactionSystem) {
|
||||
this.scene.interactionSystem.spawnClue(position.x, position.y, clue);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`📍 Placed ${this.allClues.length} clues across the world!`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get position for a biome (placeholder)
|
||||
*/
|
||||
getBiomePosition(biome) {
|
||||
// In real implementation, would use biome system
|
||||
const biomePositions = {
|
||||
forest: { x: 1000, y: 1000 },
|
||||
wasteland: { x: 3000, y: 1500 },
|
||||
swamp: { x: 2000, y: 3000 },
|
||||
mountains: { x: 4000, y: 500 },
|
||||
hope_valley: { x: 500, y: 500 },
|
||||
crystal_caves: { x: 3500, y: 2500 },
|
||||
portal_ruins: { x: 2500, y: 2000 },
|
||||
laboratory: { x: 3200, y: 1800 },
|
||||
medical_facility: { x: 2800, y: 2200 },
|
||||
abandoned_house: { x: 1500, y: 1200 },
|
||||
atlantis: { x: 5000, y: 4000 },
|
||||
chernobyl: { x: 5500, y: 2000 },
|
||||
amazon: { x: 4500, y: 3500 },
|
||||
dark_fortress: { x: 6000, y: 1000 },
|
||||
final_castle: { x: 7000, y: 500 }
|
||||
};
|
||||
|
||||
return biomePositions[biome] || { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover a clue
|
||||
*/
|
||||
discoverClue(clueId) {
|
||||
if (this.discovered.has(clueId)) {
|
||||
console.log('ℹ️ Clue already discovered!');
|
||||
return false;
|
||||
}
|
||||
|
||||
const clue = this.allClues.find(c => c.id === clueId);
|
||||
if (!clue) {
|
||||
console.log(`❌ Clue not found: ${clueId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.discovered.add(clueId);
|
||||
|
||||
console.log(`💜 DISCOVERED: ${clue.title} (${this.discovered.size}/50)`);
|
||||
|
||||
// Play discovery cutscene
|
||||
this.playDiscoveryCutscene(clue);
|
||||
|
||||
// Check story progression
|
||||
this.checkStoryUnlocks();
|
||||
|
||||
// Trigger Twin Bond reaction
|
||||
if (this.scene.twinBondSystem) {
|
||||
this.scene.twinBondSystem.triggerClueReaction(clue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play emotional discovery cutscene
|
||||
*/
|
||||
playDiscoveryCutscene(clue) {
|
||||
console.log(`🎬 CUTSCENE: Discovering "${clue.title}"`);
|
||||
|
||||
// Emotional reaction based on clue type
|
||||
let kaiReaction = '';
|
||||
|
||||
switch (clue.type) {
|
||||
case 'message':
|
||||
kaiReaction = this.getMessageReaction(clue);
|
||||
break;
|
||||
case 'photo':
|
||||
kaiReaction = this.getPhotoReaction(clue);
|
||||
break;
|
||||
case 'item':
|
||||
kaiReaction = this.getItemReaction(clue);
|
||||
break;
|
||||
}
|
||||
|
||||
// Show cutscene UI
|
||||
this.scene.events.emit('show-cutscene', {
|
||||
type: 'clue_discovery',
|
||||
clue: clue,
|
||||
reaction: kaiReaction,
|
||||
bondActivation: this.discovered.size % 5 === 0 // Twin Bond activates every 5 clues
|
||||
});
|
||||
|
||||
// Play emotional music
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playEmotionalTheme();
|
||||
}
|
||||
|
||||
console.log(`💭 Kai: "${kaiReaction}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Kai's reaction to a message
|
||||
*/
|
||||
getMessageReaction(clue) {
|
||||
const reactions = {
|
||||
msg_01: "Ana... I'll find you. I promise.",
|
||||
msg_02: "Red flowers... I'll follow your trail anywhere.",
|
||||
msg_03: "Intelligence? Could we... coexist with them?",
|
||||
msg_04: "The Giant Troll King. I'll face him for you, Ana.",
|
||||
msg_05: "We WILL rebuild Hope Valley together. I swear it.",
|
||||
msg_06: "I feel you too, Ana. Our bond will guide me.",
|
||||
msg_07: "Portals... this could change everything!",
|
||||
msg_08: "Domestication? This changes how I see them...",
|
||||
msg_09: "A cure! There's still hope for this world!",
|
||||
msg_10: "A family... Ana, we'll have that future together.",
|
||||
msg_11: "Atlantis! Your curiosity never stopped, did it?",
|
||||
msg_12: "Radiation won't stop me. Nothing will.",
|
||||
msg_13: "Even captured, you appreciate nature's beauty...",
|
||||
msg_14: "I'll NEVER give up on you, Ana. Never!",
|
||||
msg_15: "I love you too, sister. I'm coming for you."
|
||||
};
|
||||
|
||||
return reactions[clue.id] || "Ana... another piece of you found.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Kai's reaction to a photo
|
||||
*/
|
||||
getPhotoReaction(clue) {
|
||||
const reactions = {
|
||||
photo_01: "*tears up* We were so innocent back then...",
|
||||
photo_02: "So that's the monster who took you...",
|
||||
photo_03: "Hope Valley was paradise. It will be again.",
|
||||
photo_04: "Even then, you saw them differently than others.",
|
||||
photo_05: "Your map will help me repair them all!",
|
||||
photo_06: "You documented everything. So thorough, Ana.",
|
||||
photo_07: "Your handwriting... I'd recognize it anywhere.",
|
||||
photo_08: "Atlantean tech! This could save us all!",
|
||||
photo_09: "You went to Chernobyl?! Ana...",
|
||||
photo_10: "Ancient mysteries. You loved this stuff.",
|
||||
photo_11: "*clenches fist* I see you, captor. I'm coming.",
|
||||
photo_12: "*breaks down* I'm coming, Ana. Hold on!"
|
||||
};
|
||||
|
||||
return reactions[clue.id] || "Another memory of you...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Kai's reaction to an item
|
||||
*/
|
||||
getItemReaction(clue) {
|
||||
const reactions = {
|
||||
item_01: "*holds bracelet* I have the matching one...",
|
||||
item_09: "*clutches necklace* Mother's necklace! You kept it safe.",
|
||||
item_13: "*pairs gloves* Both found. You left a trail for me.",
|
||||
item_17: "*opens locket* My photo... you carried me with you.",
|
||||
item_23: "*trembling hands* I'll open this when I find you, Ana."
|
||||
};
|
||||
|
||||
return reactions[clue.id] || `${clue.title}... you were here.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if story unlocks should trigger
|
||||
*/
|
||||
checkStoryUnlocks() {
|
||||
const count = this.discovered.size;
|
||||
|
||||
Object.keys(this.storyUnlocks).forEach(threshold => {
|
||||
const unlockId = this.storyUnlocks[threshold];
|
||||
|
||||
if (count >= parseInt(threshold)) {
|
||||
this.unlockStoryEvent(unlockId, parseInt(threshold));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock story event
|
||||
*/
|
||||
unlockStoryEvent(eventId, clueCount) {
|
||||
console.log(`🎭 STORY UNLOCK: ${eventId} (${clueCount} clues)`);
|
||||
|
||||
const events = {
|
||||
act1_context: {
|
||||
title: 'Act 1 Revealed',
|
||||
message: 'The attack, the kidnapping... the full picture emerges.',
|
||||
reward: 'Twin Bond Level +1'
|
||||
},
|
||||
zmaj_volk_reveal: {
|
||||
title: 'Giant Troll King\'s Identity',
|
||||
message: 'The true nature of the captor revealed.',
|
||||
reward: 'Boss Location Marked'
|
||||
},
|
||||
twin_bond_boost: {
|
||||
title: 'Twin Bond Strengthens',
|
||||
message: 'Your psychic connection with Ana intensifies!',
|
||||
reward: 'Twin Bond abilities enhanced'
|
||||
},
|
||||
act2_context: {
|
||||
title: 'Act 2 Begins',
|
||||
message: 'Ana\'s journey takes unexpected turns...',
|
||||
reward: 'New locations unlocked'
|
||||
},
|
||||
final_location_hint: {
|
||||
title: 'Ana\'s Location',
|
||||
message: 'You sense where she is... the final castle.',
|
||||
reward: 'Final Castle location revealed'
|
||||
},
|
||||
true_ending_unlock: {
|
||||
title: 'ALL CLUES FOUND!',
|
||||
message: 'Every piece of Ana discovered. True Ending available!',
|
||||
reward: 'True Ending unlocked'
|
||||
}
|
||||
};
|
||||
|
||||
const event = events[eventId];
|
||||
if (event) {
|
||||
this.scene.events.emit('story-unlock', event);
|
||||
|
||||
if (this.scene.soundManager) {
|
||||
this.scene.soundManager.playDramatic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collection progress
|
||||
*/
|
||||
getProgress() {
|
||||
return {
|
||||
total: this.allClues.length,
|
||||
discovered: this.discovered.size,
|
||||
percentage: Math.round((this.discovered.size / this.allClues.length) * 100),
|
||||
messages: this.getTypeProgress('message'),
|
||||
photos: this.getTypeProgress('photo'),
|
||||
items: this.getTypeProgress('item')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress by type
|
||||
*/
|
||||
getTypeProgress(type) {
|
||||
const typeClues = this.allClues.filter(c => c.type === type);
|
||||
const discovered = typeClues.filter(c => this.discovered.has(c.id)).length;
|
||||
|
||||
return {
|
||||
total: typeClues.length,
|
||||
discovered: discovered,
|
||||
percentage: Math.round((discovered / typeClues.length) * 100)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show collection UI
|
||||
*/
|
||||
showCollectionUI() {
|
||||
const progress = this.getProgress();
|
||||
|
||||
console.log('💜 ANA\'S CLUES COLLECTION');
|
||||
console.log(`Total: ${progress.discovered}/${progress.total} (${progress.percentage}%)`);
|
||||
console.log('─────────────────────────────────');
|
||||
console.log(`📝 Messages: ${progress.messages.discovered}/${progress.messages.total} (${progress.messages.percentage}%)`);
|
||||
console.log(`📷 Photos: ${progress.photos.discovered}/${progress.photos.total} (${progress.photos.percentage}%)`);
|
||||
console.log(`💎 Items: ${progress.items.discovered}/${progress.items.total} (${progress.items.percentage}%)`);
|
||||
|
||||
// Emit UI event
|
||||
this.scene.events.emit('show-clue-collection', {
|
||||
progress: progress,
|
||||
clues: this.allClues,
|
||||
discovered: Array.from(this.discovered)
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user