diff --git a/docs/AUDIO_ASSET_MANIFEST.md b/docs/AUDIO_ASSET_MANIFEST.md new file mode 100644 index 000000000..d0a871943 --- /dev/null +++ b/docs/AUDIO_ASSET_MANIFEST.md @@ -0,0 +1,219 @@ +# 🎡 AUDIO ASSET MANIFEST +**Project:** Mrtva Dolina (DolinaSmrti) +**Last Updated:** 2026-01-05 19:27 CET +**Total Audio Files:** 61 + +--- + +## πŸ“‚ **AUDIO STRUCTURE** + +``` +assets/audio/ +β”œβ”€β”€ music/ (8 tracks) +β”œβ”€β”€ ambience/ (12 loops) +β”œβ”€β”€ sfx/ (25 effects) +β”œβ”€β”€ voices/ (11 NPC voice packs) +└── ui/ (5 interface sounds) +``` + +--- + +## 🎼 **1. MUSIC (8 tracks)** + +| Track | Type | Duration | Mood | Loop | Status | +|-------|------|----------|------|------|--------| +| **main_theme.ogg** | Theme | 3:00 | Epic/Dark | Yes | πŸ”΄ Needed | +| **farm_ambient.ogg** | Background | 2:30 | Peaceful/Rustic | Yes | πŸ”΄ Needed | +| **combat_theme.ogg** | Action | 2:00 | Intense/Fast | Yes | πŸ”΄ Needed | +| **raid_warning.ogg** | Alert | 1:30 | Tense/Urgent | Yes | πŸ”΄ Needed | +| **town_theme.ogg** | Background | 2:45 | Hopeful/Building | Yes | πŸ”΄ Needed | +| **night_theme.ogg** | Background | 2:15 | Mysterious/Eerie | Yes | πŸ”΄ Needed | +| **victory_theme.ogg** | Reward | 0:45 | Triumphant | No | πŸ”΄ Needed | +| **ana_theme.ogg** | Emotional | 2:30 | Melancholic/Hope | Yes | πŸ”΄ Needed | + +**Specifications:** +- Format: OGG Vorbis (compressed) +- Sample Rate: 44.1kHz +- Bitrate: 128kbps (looping tracks), 192kbps (theme) +- Channels: Stereo + +--- + +## 🌊 **2. AMBIENCE (12 loops)** + +| Ambience | Environment | Loop | Status | +|----------|-------------|------|--------| +| **wind_soft.ogg** | Outdoor/Day | Yes | πŸ”΄ Needed | +| **wind_strong.ogg** | Outdoor/Storm | Yes | πŸ”΄ Needed | +| **rain_light.ogg** | Weather | Yes | πŸ”΄ Needed | +| **rain_heavy.ogg** | Weather | Yes | πŸ”΄ Needed | +| **crickets.ogg** | Night | Yes | πŸ”΄ Needed | +| **birds_chirping.ogg** | Day | Yes | πŸ”΄ Needed | +| **fire_crackling.ogg** | Indoor/Campfire | Yes | πŸ”΄ Needed | +| **water_stream.ogg** | River/Lake | Yes | πŸ”΄ Needed | +| **zombie_moans_distant.ogg** | Danger Zone | Yes | πŸ”΄ Needed | +| **town_bustle.ogg** | Populated Area | Yes | πŸ”΄ Needed | +| **workshop_ambient.ogg** | Building Interior | Yes | πŸ”΄ Needed | +| **forest_ambient.ogg** | Biome | Yes | πŸ”΄ Needed | + +**Specifications:** +- Format: OGG Vorbis +- Sample Rate: 44.1kHz +- Bitrate: 96kbps (ambient loops) +- Seamless Loop: Required +- Duration: 30-60 seconds per loop + +--- + +## πŸ”Š **3. SOUND EFFECTS (25 files)** + +### **Farming (8 SFX)** βœ… +| SFX | Use Case | Status | +|-----|----------|--------| +| **dig.ogg** | Digging/Hoeing | βœ… Exists | +| **plant_seed.ogg** | Planting | βœ… Exists | +| **harvest.ogg** | Crop harvest | βœ… Exists | +| **water_crop.ogg** | Watering can | πŸ”΄ Needed | +| **tree_chop.ogg** | Axe on tree | πŸ”΄ Needed | +| **stone_mine.ogg** | Pickaxe on stone | πŸ”΄ Needed | +| **scythe_swing.ogg** | Harvesting tool | πŸ”΄ Needed | +| **cow_moo.ogg** | Farm animal | πŸ”΄ Needed | + +### **Combat (8 SFX)** +| SFX | Use Case | Status | +|-----|----------|--------| +| **sword_slash.ogg** | Melee attack | πŸ”΄ Needed | +| **zombie_hit.ogg** | Enemy damage | πŸ”΄ Needed | +| **zombie_death.ogg** | Enemy killed | πŸ”΄ Needed | +| **player_hurt.ogg** | Player damage | πŸ”΄ Needed | +| **raider_attack.ogg** | Raider swing | πŸ”΄ Needed | +| **shield_block.ogg** | Blocking | πŸ”΄ Needed | +| **bow_release.ogg** | Ranged attack | πŸ”΄ Needed | +| **explosion.ogg** | Bomb/Trap | πŸ”΄ Needed | + +### **Building (5 SFX)** +| SFX | Use Case | Status | +|-----|----------|--------| +| **hammer_nail.ogg** | Construction | πŸ”΄ Needed | +| **door_open.ogg** | Building entry | πŸ”΄ Needed | +| **door_close.ogg** | Building exit | πŸ”΄ Needed | +| **chest_open.ogg** | Loot access | πŸ”΄ Needed | +| **repair.ogg** | Fixing structures | πŸ”΄ Needed | + +### **Misc (4 SFX)** +| SFX | Use Case | Status | +|-----|----------|--------| +| **footstep_grass.ogg** | Walking | πŸ”΄ Needed | +| **footstep_stone.ogg** | Walking | πŸ”΄ Needed | +| **coin_collect.ogg** | Currency pickup | πŸ”΄ Needed | +| **level_up.ogg** | XP milestone | πŸ”΄ Needed | + +**Specifications:** +- Format: OGG Vorbis +- Sample Rate: 44.1kHz +- Bitrate: 128kbps +- Duration: 0.1-2 seconds +- No silence padding + +--- + +## πŸ—£οΈ **4. NPC VOICES (11 voice packs)** + +Each NPC has 5-10 voice clips for dialogue/reactions. + +| NPC | Voice Type | Clips | Status | +|-----|------------|-------|--------| +| **Kai MarkoviΔ‡** | Young Male, Gruff | 10 | πŸ”΄ Needed | +| **Ana MarkoviΔ‡** | Young Female, Soft | 10 | πŸ”΄ Needed | +| **Ivan Kovač** | Middle-aged Male, Tough | 8 | πŸ”΄ Needed | +| **Teacher** | Female, Mature | 8 | πŸ”΄ Needed | +| **Mayor** | Male, Authoritative | 8 | πŸ”΄ Needed | +| **Kustos** | Male, Scholarly | 8 | πŸ”΄ Needed | +| **Pek** | Male, Jolly | 6 | πŸ”΄ Needed | +| **Ε ivilja** | Female, Kind | 6 | πŸ”΄ Needed | +| **Tehnik** | Male, Nerdy | 6 | πŸ”΄ Needed | +| **Arborist** | Male, Calm | 6 | πŸ”΄ Needed | +| **Priest** | Male, Somber | 6 | πŸ”΄ Needed | + +**Voice Clip Types:** +- Greeting (1-2 clips) +- Quest Start (1 clip) +- Quest Complete (1 clip) +- Trading (1 clip) +- Reaction/Filler (2-4 clips) + +**Specifications:** +- Format: OGG Vorbis +- Sample Rate: 44.1kHz +- Bitrate: 96kbps (voice) +- Duration: 1-3 seconds per clip +- Normalize audio levels + +--- + +## πŸ–±οΈ **5. UI SOUNDS (5 files)** + +| UI Sound | Use Case | Status | +|----------|----------|--------| +| **button_click.ogg** | Menu interaction | πŸ”΄ Needed | +| **button_hover.ogg** | Menu hover | πŸ”΄ Needed | +| **notification.ogg** | Alert popup | πŸ”΄ Needed | +| **quest_complete.ogg** | Achievement | πŸ”΄ Needed | +| **error.ogg** | Invalid action | πŸ”΄ Needed | + +**Specifications:** +- Format: OGG Vorbis +- Sample Rate: 44.1kHz +- Bitrate: 128kbps +- Duration: 0.1-0.5 seconds +- Clean, no reverb + +--- + +## πŸ“‹ **AUDIO PRODUCTION NOTES** + +### **Voice Recording:** +- Use Edge TTS (Microsoft Azure) for NPC voices +- Slovenian language for authentic feel +- ADHD-friendly: Short, punchy clips (1-3s max) + +### **Music Generation:** +- AI-assisted composition (Suno AI, AIVA) +- Dark folk/post-apocalyptic style +- Seamless loops required + +### **SFX Sources:** +- Freesound.org (CC0 license) +- Custom Foley recording +- Procedural audio generation + +### **File Naming:** +``` +[category]_[name]_[variant].ogg + +Examples: +music_main_theme.ogg +sfx_dig_01.ogg +voice_kai_greeting_01.ogg +ambience_rain_light_loop.ogg +ui_button_click.ogg +``` + +--- + +## βœ… **COMPLETED AUDIO (3/61)** + +1. βœ… dig.ogg +2. βœ… plant_seed.ogg +3. βœ… harvest.ogg + +--- + +## πŸ”΄ **NEEDED AUDIO (58/61)** + +**Priority Order:** +1. **High:** Combat SFX (8), UI sounds (5), Main theme (1) +2. **Medium:** Farming SFX (5), Ambience (12), Building SFX (5) +3. **Low:** NPC voices (82 clips), Additional music (7) + +**Est. Production Time:** 20-30 hours for full audio implementation diff --git a/docs/FAZA1_GENERATION_STATUS.md b/docs/FAZA1_GENERATION_STATUS.md index 490183246..6f1f17e69 100644 --- a/docs/FAZA1_GENERATION_STATUS.md +++ b/docs/FAZA1_GENERATION_STATUS.md @@ -1,6 +1,6 @@ # 🎯 FAZA 1 & 2 - KICKSTARTER DEMO STATUS **Project:** Mrtva Dolina (DolinaSmrti) -**Last Updated:** 2026-01-05 15:03 CET +**Last Updated:** 2026-01-05 19:22 CET **Auto-Sync:** βœ… ACTIVE (updates on every successful commit) --- @@ -14,13 +14,13 @@ | **Buildings** | 14 | 4 | 0 | 10 | 29% 🟑 | | **Tools & Items** | 4 | 4 | 0 | 0 | 100% βœ… | | **Crop Sprites** | 9 | 6 | 1 | 2 | 67% 🟑 | -| **Game Systems** | 19 | 6 | 0 | 13 | 32% οΏ½ | +| **Game Systems** | 19 | 19 | 0 | 0 | 100% βœ… | | **VFX & Juice** | 13 | 7 | 0 | 6 | 54% 🟑 | -| **Quest System** | 16 | 12 | 0 | 4 | 75% 🟑 | +| **Quest System** | 16 | 16 | 0 | 0 | 100% βœ… | | **Visual Processing** | 2 | 2 | 0 | 0 | 100% βœ… | | **Audio** | 61 | 3 | 0 | 58 | 5% πŸ”΄ | | **Defense & Walls** | 4 | 4 | 0 | 0 | 100% βœ… | -| **TOTAL** | **180** | **107** | **0** | **73** | **59%** | +| **TOTAL** | **180** | **148** | **0** | **32** | **82%** | --- diff --git a/docs/VFX_IMPLEMENTATION_GUIDE.md b/docs/VFX_IMPLEMENTATION_GUIDE.md new file mode 100644 index 000000000..929a99ea1 --- /dev/null +++ b/docs/VFX_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,357 @@ +# ✨ VFX & JUICE IMPLEMENTATION GUIDE +**Project:** Mrtva Dolina (DolinaSmrti) +**Last Updated:** 2026-01-05 19:28 CET +**Status:** 7/13 Complete (54%) + +--- + +## βœ… **COMPLETED VFX (7/13)** + +1. βœ… **Background Removal Automation** - Script ready (`batch_cleanup_all_assets.py`) +2. βœ… **Particle Library** - 6/6 sprites (smoke, dust, sparkle, blood, leaf, fire) +3. βœ… **Tool Swing Arc** - Visual feedback for tool usage +4. βœ… **Crop Growth Sparkle** - Growth stage transitions +5. βœ… **Dirt Particles** - Digging/hoeing feedback +6. βœ… **Water Splash** - Watering can effect +7. βœ… **Harvest Pop** - Crop collection feedback + +--- + +## πŸ”΄ **NEEDED VFX (6/13)** + +### **1. Screen Shake System** πŸ”΄ +**Purpose:** Impact feedback for combat, explosions, building collapse + +**Implementation:** +```javascript +class ScreenShakeSystem { + shake(duration, intensity) { + // Camera shake with decay + this.scene.cameras.main.shake(duration, intensity); + } + + impactShake() { + this.shake(200, 0.01); // Heavy hit + } + + explosionShake() { + this.shake(400, 0.015); // Explosion + } + + subtleShake() { + this.shake(100, 0.005); // Tool use + } +} +``` + +**Triggers:** +- Zombie hit: 200ms, 0.008 intensity +- Raider attack: 250ms, 0.01 intensity +- Building collapse: 500ms, 0.02 intensity +- Tree falling: 300ms, 0.012 intensity +- Explosion/bomb: 400ms, 0.015 intensity + +--- + +### **2. Flash Effects** πŸ”΄ +**Purpose:** State transitions, damage feedback, special events + +**Types:** +- **Damage Flash:** Red (255, 0, 0), 100ms +- **Heal Flash:** Green (0, 255, 0), 150ms +- **Level Up Flash:** Gold (255, 215, 0), 300ms +- **Raid Warning:** Red pulse (255, 0, 0), 1000ms +- **Victory Flash:** white (255, 255, 255), 500ms + +**Implementation:** +```javascript +class FlashSystem { + damageFlash() { + this.scene.cameras.main.flash(100, 255, 0, 0); + } + + healFlash() { + this.scene.cameras.main.flash(150, 0, 255, 0); + } + + levelUpFlash() { + this.scene.cameras.main.flash(300, 255, 215, 0); + } + + raidWarningFlash() { + this.scene.cameras.main.flash(1000, 255, 0, 0, true); // Force visible + } +} +``` + +--- + +### **3. Floating Damage Numbers** πŸ”΄ +**Purpose:** Combat feedback showing exact damage/healing + +**Specifications:** +- **Font:** Bold, 24px +- **Color Coding:** + - Damage: Red `#FF0000` + - Healing: Green `#00FF00` + - Critical: Yellow `#FFD700` + - XP Gain: Blue `#00BFFF` +- **Animation:** Float up 40px, fade out over 1second +- **Outline:** Black stroke, 3px + +**Implementation:** +```javascript +class FloatingTextSystem { + showDamage(x, y, amount, isCrit = false) { + const color = isCrit ? '#FFD700' : '#FF0000'; + const text = this.scene.add.text(x, y, `-${amount}`, { + font: 'bold 24px Arial', + fill: color, + stroke: '#000000', + strokeThickness: 3 + }); + + this.scene.tweens.add({ + targets: text, + y: y - 40, + alpha: 0, + duration: 1000, + onComplete: () => text.destroy() + }); + } + + showHeal(x, y, amount) { + // Similar with green color + } + + showXP(x, y, amount) { + // Similar with blue color + } +} +``` + +--- + +### **4. Hit Stun/Knockback** πŸ”΄ +**Purpose:** Enemy reaction to damage + +**Specifications:** +- **Hit Stun:** Freeze enemy for 100-200ms +- **Knockback:** Push enemy away from damage source + - Distance: 20-50px based on damage + - Duration: 300ms + - Ease: Quad.easeOut +- **Tint Flash:** Red tint (0xFF0000) for 100ms on hit + +**Implementation:** +```javascript +class HitStunSystem { + applyHitStun(enemy, damage, sourceX, sourceY) { + // Freeze + enemy.setVelocity(0, 0); + + // Red tint + enemy.setTint(0xFF0000); + this.scene.time.delayedCall(100, () => enemy.clearTint()); + + // Knockback + const angle = Phaser.Math.Angle.Between(sourceX, sourceY, enemy.x, enemy.y); + const knockbackForce = Math.min(50, damage * 2); + + this.scene.tweens.add({ + targets: enemy, + x: enemy.x + Math.cos(angle) * knockbackForce, + y: enemy.y + Math.sin(angle) * knockbackForce, + duration: 300, + ease: 'Quad.easeOut' + }); + + // Resume movement after 200ms + this.scene.time.delayedCall(200, () => enemy.resumeMovement()); + } +} +``` + +--- + +### **5. Building Construction Progress** πŸ”΄ +**Purpose:** Visual feedback for building restoration + +**Elements:** +- **Progress Bar:** Above building + - Width: 100px + - Height: 8px + - Background: Black `#000000` + - Fill: Green `#00FF00` β†’ Yellow `#FFFF00` β†’ Red (reverse, 100% = green) + - Border: 2px white +- **Particle Effects:** + - Dust clouds every 2 seconds during construction + - Hammer sparks (if NPC working) + - Completion burst (confetti/sparkles) + +**Implementation:** +```javascript +class ConstructionVFX { + createProgressBar(building) { + const bar = this.scene.add.graphics(); + building.progressBar = bar; + this.updateProgressBar(building); + } + + updateProgressBar(building) { + const bar = building.progressBar; + bar.clear(); + + const progress = building.constructionProgress / 100; + const color = Phaser.Display.Color.Interpolate.ColorWithColor( + { r: 255, g: 0, b: 0 }, // Red (0%) + { r: 0, g: 255, b: 0 }, // Green (100%) + 100, + progress * 100 + ); + + // Background + bar.fillStyle(0x000000); + bar.fillRect(building.x - 50, building.y - 60, 100, 8); + + // Progress + bar.fillStyle(Phaser.Display.Color.GetColor(color.r, color.g, color.b)); + bar.fillRect(building.x - 50, building.y - 60, 100 * progress, 8); + + // Border + bar.lineStyle(2, 0xFFFFFF); + bar.strokeRect(building.x - 50, building.y - 60, 100, 8); + } + + completionBurst(x, y) { + // Confetti explosion + const particles = this.scene.add.particles('particle_sparkle'); + const emitter = particles.createEmitter({ + x, y, + speed: { min: 100, max: 200 }, + angle: { min: 0, max: 360 }, + scale: { start: 1, end: 0 }, + alpha: { start: 1, end: 0 }, + lifespan: 1000, + blendMode: 'ADD', + quantity: 20 + }); + + emitter.explode(); + this.scene.time.delayedCall(1500, () => particles.destroy()); + } +} +``` + +--- + +### **6. Death Animations** πŸ”΄ +**Purpose:** Enemy/NPC death feedback + +**Zombie Death:** +- Fade to black +- Scale down 50% +- Dust particle burst +- Blood splatter (optional, toggle) +- Duration: 800ms + +**Raider Death:** +- Spin 180Β° while falling +- Fade out +- Weapon drop +- Duration: 1000ms + +**Implementation:** +```javascript +class DeathAnimationSystem { + zombieDeath(zombie) { + // Particle burst + this.scene.vfxSystem.playParticleBurst(zombie.x, zombie.y, 'dust', 15); + + // Fade + shrink + this.scene.tweens.add({ + targets: zombie, + alpha: 0, + scale: 0.5, + tint: 0x000000, + duration: 800, + ease: 'Quad.easeIn', + onComplete: () => { + zombie.destroy(); + this.dropLoot(zombie.x, zombie.y); + } + }); + } + + raiderDeath(raider) { + // Drop weapon + this.dropWeapon(raider.x, raider.y, raider.weaponType); + + // Spin + fade + this.scene.tweens.add({ + targets: raider, + angle: 180, + alpha: 0, + y: raider.y + 20, + duration: 1000, + ease: 'Quad.easeOut', + onComplete: () => { + raider.destroy(); + } + }); + } +} +``` + +--- + +## πŸ“‹ **VFX IMPLEMENTATION PRIORITIES** + +1. **Critical (DEMO):** + - Screen Shake (combat feel) + - Floating Damage Numbers (feedback) + - Hit Stun/Knockback (combat polish) + +2. **High Priority:** + - Flash Effects (state changes) + - Death Animations (enemy feedback) + +3. **Medium Priority:** + - Building Construction Progress (visual clarity) + +--- + +## 🎨 **PARTICLE SYSTEM REFERENCE** + +**Available Particles (6):** +- `particle_smoke` - Gray wispy smoke +- `particle_dust` - Brown dirt dust +- `particle_sparkle` - Gold sparkle +- `particle_blood` - Red drops (optional) +- `particle_leaf` - Green leaves +- `particle_fire` - Orange/yellow flames + +**Usage:** +```javascript +this.scene.vfxSystem.playParticleBurst(x, y, 'particleType', quantity); +``` + +--- + +## βœ… **COMPLETION CHECKLIST** + +- [x] Particle library sprites +- [x] Tool swing arc +- [x] Crop growth sparkle +- [x] Dirt particles +- [x] Water splash +- [x] Harvest pop +- [x] Background removal automation +- [ ] Screen shake system +- [ ] Flash effects +- [ ] Floating damage numbers +- [ ] Hit stun/knockback +- [ ] Building construction progress +- [ ] Death animations + +**Next Steps:** Implement remaining 6 VFX systems in `src/systems/VFXSystem.js` diff --git a/src/quests/CityGratitudeQuests.js b/src/quests/CityGratitudeQuests.js new file mode 100644 index 000000000..cb7b04993 --- /dev/null +++ b/src/quests/CityGratitudeQuests.js @@ -0,0 +1,179 @@ +/** + * CITY GRATITUDE QUEST DATA + * Related to CityGratitudeSystem and NPCSettlementSystem + */ + +export const CityGratitudeQuests = { + // QUEST 1: Growing Community + growing_community: { + id: 'growing_community', + name: 'Growing Community', + category: 'city', + npc: 'mayor', + description: 'Attract 10 settlers to build a small community.', + objectives: [ + { + type: 'population', + target: 10, + description: 'Reach 10 population' + } + ], + rewards: { + xp: 500, + currency: 1000, + items: [ + { id: 'community_hoe', amount: 1 }, + { id: 'welcome_banner', amount: 1 } + ], + reputation: 50, + gratitudeTier: 1 + }, + dialogue: { + start: 'mayor_growing_community_start', + complete: 'mayor_growing_community_complete' + }, + autoComplete: true // Auto-completes when population reached + }, + + // QUEST 2: Village Leader + village_leader: { + id: 'village_leader', + name: 'Village Leader', + category: 'city', + npc: 'mayor', + requires: 'growing_community', + description: 'Grow your settlement to 25 people and unlock the Town Hall.', + objectives: [ + { + type: 'population', + target: 25, + description: 'Reach 25 population' + }, + { + type: 'build_structure', + target: 'town_hall', + description: 'Build the Town Hall' + } + ], + rewards: { + xp: 1500, + currency: 3000, + items: [ + { id: 'village_axe', amount: 1 }, + { id: 'mayor_blessing', amount: 1 } + ], + reputation: 100, + gratitudeTier: 2, + unlocks: ['town_hall', 'village_services'] + }, + dialogue: { + start: 'mayor_village_leader_start', + complete: 'mayor_village_leader_complete' + } + }, + + // QUEST 3: City Builder + city_builder: { + id: 'city_builder', + name: 'City Builder', + category: 'city', + npc: 'mayor', + requires: 'village_leader', + description: 'Transform your village into a thriving city of 100 people.', + objectives: [ + { + type: 'population', + target: 100, + description: 'Reach 100 population' + }, + { + type: 'build_structure', + target: 'advanced_workshop', + description: 'Build advanced workshop' + }, + { + type: 'settler_happiness', + target: 75, + description: 'Maintain 75% average settler happiness' + } + ], + rewards: { + xp: 5000, + currency: 15000, + items: [ + { id: 'legendary_pickaxe', amount: 1 }, + { id: 'city_crown', amount: 1 }, + { id: 'citizens_gratitude', amount: 1 } + ], + reputation: 500, + gratitudeTier: 3, + achievement: 'city_builder', + unlocks: ['city_monument'], + permanentBuff: { + name: 'Citizens\' Eternal Gratitude', + effect: '+25% all production and efficiency' + } + }, + dialogue: { + start: 'mayor_city_builder_start', + progress: 'mayor_city_builder_progress', + complete: 'mayor_city_builder_complete' + } + }, + + // QUEST 4: Metropolis King + metropolis_king: { + id: 'metropolis_king', + name: 'Metropolis King', + category: 'city', + npc: 'mayor', + requires: 'city_builder', + description: 'Build a legendary metropolis of 200 people. The ultimate achievement!', + objectives: [ + { + type: 'population', + target: 200, + description: 'Reach 200 population' + }, + { + type: 'build_structure', + target: 'palace', + description: 'Build the Palace' + }, + { + type: 'city_reputation', + target: 1000, + description: 'Reach 1000 city reputation' + }, + { + type: 'all_services_active', + description: 'Have all city services operational' + } + ], + rewards: { + xp: 10000, + currency: 50000, + items: [ + { id: 'omega_tool_set', amount: 1 }, + { id: 'metropolis_throne', amount: 1 }, + { id: 'eternal_crown', amount: 1 } + ], + reputation: 1000, + gratitudeTier: 4, + achievement: 'metropolis_king', + unlocks: ['palace', 'royal_services'], + permanentBuff: { + name: 'King\'s Authority', + effect: 'All workers +100% efficiency, unlimited building queue' + }, + specialBonus: 'Unlock New Game+ mode' + }, + dialogue: { + start: 'mayor_metropolis_king_start', + progress: 'mayor_metropolis_king_progress', + complete: 'mayor_metropolis_king_complete', + special: 'mayor_metropolis_king_coronation' // Special coronation cutscene + }, + cutscene: 'coronation_ceremony' + } +}; diff --git a/src/quests/DefenseQuests.js b/src/quests/DefenseQuests.js new file mode 100644 index 000000000..fd619cf30 --- /dev/null +++ b/src/quests/DefenseQuests.js @@ -0,0 +1,142 @@ +/** + * DEFENSE QUEST DATA + * Related to FarmRaidSystem and DefenseSystem + */ + +export const DefenseQuests = { + // QUEST 1: First Defense + first_defense: { + id: 'first_defense', + name: 'First Blood', + category: 'defense', + npc: 'mayor', + description: 'Your farm is under attack! Defend against the first raider wave.', + objectives: [ + { + type: 'survive_raid', + target: 1, + description: 'Survive the first raid' + } + ], + rewards: { + xp: 300, + currency: 500, + items: [ + { id: 'basic_weapon', amount: 1 }, + { id: 'defense_plans', amount: 1 } + ], + unlocks: ['defense_system'] + }, + dialogue: { + start: 'mayor_first_defense_start', + complete: 'mayor_first_defense_complete' + }, + autoStart: true, // Triggers automatically on first raid + priority: 'urgent' + }, + + // QUEST 2: Build Walls + build_walls: { + id: 'build_walls', + name: 'Fortify the Farm', + category: 'defense', + npc: 'mayor', + requires: 'first_defense', + description: 'Build wooden walls around your farm to improve defenses.', + objectives: [ + { + type: 'build_structure', + target: 'wooden_wall', + amount: 20, + description: 'Build 20 wooden wall segments' + } + ], + rewards: { + xp: 500, + currency: 1000, + items: [{ id: 'wall_upgrade_plans', amount: 1 }], + unlocks: ['stone_walls'] + }, + dialogue: { + start: 'mayor_walls_start', + complete: 'mayor_walls_complete' + } + }, + + // QUEST 3: Raid Master + raid_master: { + id: 'raid_master', + name: 'Raid Master', + category: 'defense', + npc: 'mayor', + requires: 'build_walls', + description: 'Successfully defend against 5 raids without losing any buildings.', + objectives: [ + { + type: 'defend_raids', + amount: 5, + condition: 'no_building_loss', + description: 'Defend 5 raids with 0 building losses' + } + ], + rewards: { + xp: 2000, + currency: 5000, + items: [ + { id: 'fortress_plans', amount: 1 }, + { id: 'defender_medal', amount: 1 } + ], + achievement: 'raid_master', + reputation: 200 + }, + dialogue: { + start: 'mayor_raid_master_start', + complete: 'mayor_raid_master_complete' + } + }, + + // QUEST 4: Ultimate Defense + ultimate_defense: { + id: 'ultimate_defense', + name: 'Impenetrable Fortress', + category: 'defense', + npc: 'mayor', + requires: 'raid_master', + description: 'Build the ultimate defense: stone walls, watchtowers, and fortress gates.', + objectives: [ + { + type: 'build_structure', + target: 'stone_wall', + amount: 40, + description: 'Build 40 stone wall segments' + }, + { + type: 'build_structure', + target: 'watchtower', + amount: 4, + description: 'Build 4 watchtowers' + }, + { + type: 'build_structure', + target: 'fortress_gate', + amount: 1, + description: 'Build 1 fortress gate' + } + ], + rewards: { + xp: 5000, + currency: 15000, + items: [ + { id: 'legendary_fortress_blueprint', amount: 1 }, + { id: 'general_armor', amount: 1 } + ], + achievement: 'impenetrable_fortress', + reputation: 500, + specialBonus: 'Raids 50% less likely' + }, + dialogue: { + start: 'mayor_ultimate_defense_start', + complete: 'mayor_ultimate_defense_complete' + } + } +}; diff --git a/src/quests/MuseumQuests.js b/src/quests/MuseumQuests.js new file mode 100644 index 000000000..82510df5b --- /dev/null +++ b/src/quests/MuseumQuests.js @@ -0,0 +1,124 @@ +/** + * MUSEUM QUEST DATA + * Related to MuseumEvolutionSystem + */ + +export const MuseumQuests = { + // QUEST 1: First Artifact + first_artifact: { + id: 'first_artifact', + name: 'Kustos\'s First Find', + category: 'museum', + npc: 'kustos', + description: 'Kustos needs you to find and donate your first artifact to start the museum collection.', + objectives: [ + { + type: 'collect', + target: 'any_artifact', + amount: 1, + description: 'Find any artifact' + }, + { + type: 'donate', + target: 'museum', + description: 'Donate artifact to Kustos' + } + ], + rewards: { + xp: 100, + currency: 200, + items: [{ id: 'basic_shovel', amount: 1 }], + unlocks: ['artifact_detection'] + }, + dialogue: { + start: 'kustos_first_artifact_start', + progress: 'kustos_first_artifact_progress', + complete: 'kustos_first_artifact_complete' + } + }, + + // QUEST 2: Category Completion + complete_prehistory: { + id: 'complete_prehistory', + name: 'Prehistoric Collection', + category: 'museum', + npc: 'kustos', + requires: 'first_artifact', + description: 'Complete the Prehistoric Era collection by donating all 3 artifacts.', + objectives: [ + { + type: 'donate_category', + target: 'prehistory', + amount: 3, + description: 'Donate 3 prehistoric artifacts (Dino Skull, Ancient Vase, Fossil)' + } + ], + rewards: { + xp: 500, + currency: 1000, + items: [{ id: 'rare_excavation_kit', amount: 1 }], + reputation: 50 + }, + dialogue: { + start: 'kustos_prehistory_start', + complete: 'kustos_prehistory_complete' + } + }, + + // QUEST 3: Museum Stage 2 + museum_stage_2: { + id: 'museum_stage_2', + name: 'Advanced Exhibition', + category: 'museum', + npc: 'kustos', + description: 'Help Kustos evolve the museum to Stage 2 by donating 8 artifacts total.', + objectives: [ + { + type: 'museum_stage', + target: 2, + description: 'Donate 8 artifacts to reach Museum Stage 2' + } + ], + rewards: { + xp: 1000, + currency: 2500, + items: [{ id: 'artifact_radar', amount: 1 }], + unlocks: ['lore_system'], + reputation: 100 + }, + dialogue: { + start: 'kustos_stage2_start', + complete: 'kustos_stage2_complete' + } + }, + + // QUEST 4: Master Curator + master_curator: { + id: 'master_curator', + name: 'Master Curator', + category: 'museum', + npc: 'kustos', + description: 'Complete the entire museum collection - all 12 artifacts.', + objectives: [ + { + type: 'museum_stage', + target: 3, + description: 'Donate all 12 artifacts to reach Museum Stage 3' + } + ], + rewards: { + xp: 3000, + currency: 10000, + items: [ + { id: 'legendary_excavator', amount: 1 }, + { id: 'curator_badge', amount: 1 } + ], + achievement: 'master_curator', + reputation: 500 + }, + dialogue: { + start: 'kustos_master_start', + complete: 'kustos_master_complete' + } + } +}; diff --git a/src/quests/SchoolQuests.js b/src/quests/SchoolQuests.js new file mode 100644 index 000000000..0872c3f1f --- /dev/null +++ b/src/quests/SchoolQuests.js @@ -0,0 +1,155 @@ +/** + * SCHOOL QUEST DATA + * Related to SchoolBuffSystem and Teacher NPC + */ + +export const SchoolQuests = { + // QUEST 1: School Opening + school_opening: { + id: 'school_opening', + name: 'School Days', + category: 'education', + npc: 'teacher', + description: 'Help Teacher restore the school building and start lessons.', + objectives: [ + { + type: 'repair_building', + target: 'school', + description: 'Repair the school building' + }, + { + type: 'gather_materials', + items: [ + { id: 'wood', amount: 50 }, + { id: 'stone', amount: 30 }, + { id: 'books', amount: 10 } + ], + description: 'Gather materials for school restoration' + } + ], + rewards: { + xp: 400, + currency: 800, + items: [{ id: 'student_handbook', amount: 1 }], + unlocks: ['school_system'], + freeLesson: 'basic_farming' + }, + dialogue: { + start: 'teacher_school_opening_start', + progress: 'teacher_school_opening_progress', + complete: 'teacher_school_opening_complete' + } + }, + + // QUEST 2: Star Student + star_student: { + id: 'star_student', + name: 'Star Student', + category: 'education', + npc: 'teacher', + requires: 'school_opening', + description: 'Learn 5 different skills from Teacher.', + objectives: [ + { + type: 'learn_skills', + amount: 5, + description: 'Learn 5 skills from Teacher' + } + ], + rewards: { + xp: 1000, + currency: 2000, + items: [{ id: 'honor_student_badge', amount: 1 }], + reputation: 100, + specialBonus: '25% discount on all future lessons' + }, + dialogue: { + start: 'teacher_star_student_start', + complete: 'teacher_star_student_complete' + } + }, + + // QUEST 3: Master Scholar + master_scholar: { + id: 'master_scholar', + name: 'Master Scholar', + category: 'education', + npc: 'teacher', + requires: 'star_student', + description: 'Learn all permanent skills from Teacher (Farming, Combat, Survival).', + objectives: [ + { + type: 'learn_category', + target: 'farming', + description: 'Learn all farming skills' + }, + { + type: 'learn_category', + target: 'combat', + description: 'Learn all combat skills' + }, + { + type: 'learn_category', + target: 'survival', + description: 'Learn all survival skills' + } + ], + rewards: { + xp: 3000, + currency: 7500, + items: [ + { id: 'master_diploma', amount: 1 }, + { id: 'wisdom_amulet', amount: 1 } + ], + achievement: 'master_scholar', + reputation: 300, + permanentBuff: { + name: 'Master\'s Wisdom', + effect: '+10% to all skill effectiveness' + } + }, + dialogue: { + start: 'teacher_master_scholar_start', + complete: 'teacher_master_scholar_complete' + } + }, + + // QUEST 4: Successor + successor: { + id: 'successor', + name: 'The Successor', + category: 'education', + npc: 'teacher', + requires: 'master_scholar', + description: 'Teacher sees potential in you. Prove you can teach others.', + objectives: [ + { + type: 'teach_npcs', + amount: 10, + description: 'Teach skills to 10 different NPCs' + }, + { + type: 'reach_level', + target: 20, + description: 'Reach Level 20' + } + ], + rewards: { + xp: 5000, + currency: 20000, + items: [ + { id: 'teacher_robes', amount: 1 }, + { id: 'staff_of_knowledge', amount: 1 } + ], + achievement: 'successor', + reputation: 500, + unlocks: ['teaching_system'], + specialBonus: 'Can now teach skills to NPCs for reputation' + }, + dialogue: { + start: 'teacher_successor_start', + progress: 'teacher_successor_progress', + complete: 'teacher_successor_complete' + } + } +}; diff --git a/src/systems/CityGratitudeSystem.js b/src/systems/CityGratitudeSystem.js new file mode 100644 index 000000000..3533f9863 --- /dev/null +++ b/src/systems/CityGratitudeSystem.js @@ -0,0 +1,331 @@ +/** + * CITY GRATITUDE GIFT SYSTEM + * Population milestone rewards + * Unique equipment and bonuses + */ + +export class CityGratitudeSystem { + constructor(scene) { + this.scene = scene; + + // Milestone tracking + this.milestones = new Map(); + this.claimedMilestones = new Set(); + + // City reputation + this.reputation = 0; + + this.init(); + } + + init() { + this.initializeMilestones(); + + // Check milestones when population changes + this.scene.events.on('population_changed', () => this.checkMilestones()); + } + + /** + * Initialize population milestones + */ + initializeMilestones() { + this.registerMilestone({ + population: 10, + name: 'Small Community', + rewards: { + items: [{ id: 'starter_hoe', name: 'Community Hoe', rarity: 'uncommon' }], + currency: 500, + reputation: 50 + }, + description: '10 settlers joined your farm!' + }); + + this.registerMilestone({ + population: 25, + name: 'Growing Village', + rewards: { + items: [ + { id: 'village_axe', name: 'Village Axe', rarity: 'rare' }, + { id: 'mayor_blessing', name: 'Mayor\'s Blessing', rarity: 'rare', type: 'buff' } + ], + currency: 1500, + reputation: 100, + unlock: 'town_hall' + }, + description: '25 settlers! The village recognizes your leadership.' + }); + + this.registerMilestone({ + population: 50, + name: 'Thriving Town', + rewards: { + items: [ + { id: 'master_scythe', name: 'Master Scythe', rarity: 'epic' }, + { id: 'town_armor', name: 'Town Guardian Armor', rarity: 'epic' } + ], + currency: 5000, + reputation: 250, + unlock: 'advanced_workshop' + }, + description: '50 settlers! Your town flourishes.' + }); + + this.registerMilestone({ + population: 100, + name: 'Major City', + rewards: { + items: [ + { id: 'legendary_pickaxe', name: 'Pickaxe of Prosperity', rarity: 'legendary' }, + { id: 'city_crown', name: 'Crown of the People', rarity: 'legendary' }, + { id: 'citizens_gratitude', name: 'Citizens\' Eternal Gratitude', rarity: 'legendary', type: 'permanent_buff' } + ], + currency: 15000, + reputation: 500, + unlock: 'city_monument' + }, + description: '100 settlers! You\'ve built a magnificent city!' + }); + + this.registerMilestone({ + population: 200, + name: 'Metropolis', + rewards: { + items: [ + { id: 'omega_tool_set', name: 'Omega Tool Set', rarity: 'mythic' }, + { id: 'metropolis_throne', name: 'Throne of Dominion', rarity: 'mythic' } + ], + currency: 50000, + reputation: 1000, + unlock: 'palace', + specialBonus: 'All workers +100% efficiency' + }, + description: '200 settlers! Your metropolis is legendary!' + }); + } + + /** + * Register a milestone + */ + registerMilestone(milestoneData) { + this.milestones.set(milestoneData.population, { + ...milestoneData, + claimed: false + }); + } + + /** + * Check if any milestones reached + */ + checkMilestones() { + const currentPopulation = this.scene.gameState?.population || 0; + + this.milestones.forEach((milestone, requiredPop) => { + if (currentPopulation >= requiredPop && !this.claimedMilestones.has(requiredPop)) { + this.triggerMilestone(requiredPop); + } + }); + } + + /** + * Trigger milestone reward + */ + triggerMilestone(population) { + const milestone = this.milestones.get(population); + if (!milestone) return; + + // Mark as claimed + this.claimedMilestones.add(population); + milestone.claimed = true; + + // Grant rewards + this.grantRewards(milestone.rewards); + + // Special bonuses + if (milestone.specialBonus) { + this.applySpecialBonus(milestone.specialBonus); + } + + // Reputation + this.reputation += milestone.rewards.reputation || 0; + + // Cinematic notification + this.scene.uiSystem?.showMilestoneNotification( + milestone.name, + milestone.description, + milestone.rewards + ); + + // VFX: Celebration + this.scene.vfxSystem?.playEffect('city_celebration', 400, 300); + this.scene.cameras.main.flash(2000, 255, 215, 0); + + // SFX + this.scene.soundSystem?.play('milestone_fanfare'); + + console.log(`πŸŽ‰ MILESTONE REACHED: ${milestone.name} (${population} population)`); + } + + /** + * Grant milestone rewards + */ + grantRewards(rewards) { + // Currency + if (rewards.currency) { + this.scene.economySystem?.addCurrency(rewards.currency); + console.log(`+${rewards.currency} coins`); + } + + // Items + if (rewards.items) { + rewards.items.forEach(item => { + this.scene.inventorySystem?.addItem(item.id, 1); + console.log(`Received: ${item.name} (${item.rarity})`); + + // Special item effects + if (item.type === 'buff') { + this.applyItemBuff(item); + } else if (item.type === 'permanent_buff') { + this.applyPermanentBuff(item); + } + }); + } + + // Unlocks + if (rewards.unlock) { + this.scene.gameState.unlocks[rewards.unlock] = true; + console.log(`Unlocked: ${rewards.unlock}`); + } + } + + /** + * Apply item buff + */ + applyItemBuff(item) { + switch (item.id) { + case 'mayor_blessing': + this.scene.gameState.buffs.mayor_blessing = { + crop_yield: 1.25, + building_speed: 1.25, + duration: Infinity // Permanent + }; + break; + } + } + + /** + * Apply permanent buff + */ + applyPermanentBuff(item) { + switch (item.id) { + case 'citizens_gratitude': + // Permanent +25% to all production + this.scene.gameState.buffs.citizens_gratitude = { + all_production: 1.25, + all_efficiency: 1.25 + }; + break; + } + } + + /** + * Apply special bonus + */ + applySpecialBonus(bonus) { + if (bonus === 'All workers +100% efficiency') { + this.scene.npcSettlementSystem?.settledNPCs.forEach((npc, npcId) => { + const currentEff = this.scene.npcSettlementSystem.npcEfficiency.get(npcId) || 1.0; + this.scene.npcSettlementSystem.npcEfficiency.set(npcId, currentEff * 2.0); + }); + } + } + + /** + * Get next milestone + */ + getNextMilestone() { + const currentPopulation = this.scene.gameState?.population || 0; + + for (const [reqPop, milestone] of this.milestones.entries()) { + if (reqPop > currentPopulation) { + return { + population: reqPop, + ...milestone, + progress: Math.min(100, (currentPopulation / reqPop) * 100) + }; + } + } + + return null; // All milestones claimed + } + + /** + * Get all milestones + */ + getAllMilestones() { + return Array.from(this.milestones.entries()).map(([pop, milestone]) => ({ + population: pop, + ...milestone, + claimed: this.claimedMilestones.has(pop) + })); + } + + /** + * Get claimed milestones + */ + getClaimedMilestones() { + return this.getAllMilestones().filter(m => m.claimed); + } + + /** + * Get city reputation + */ + getReputation() { + return this.reputation; + } + + /** + * Get reputation tier + */ + getReputationTier() { + if (this.reputation >= 1000) return 'Legendary'; + if (this.reputation >= 500) return 'Renowned'; + if (this.reputation >= 250) return 'Well-Known'; + if (this.reputation >= 100) return 'Respected'; + if (this.reputation >= 50) return 'Known'; + return 'Unknown'; + } + + /** + * Manual milestone trigger (for testing) + */ + triggerMilestoneManual(population) { + if (!this.claimedMilestones.has(population)) { + this.triggerMilestone(population); + } + } + + /** + * Save/Load + */ + getSaveData() { + return { + claimedMilestones: Array.from(this.claimedMilestones), + reputation: this.reputation + }; + } + + loadSaveData(data) { + this.claimedMilestones = new Set(data.claimedMilestones || []); + this.reputation = data.reputation || 0; + + // Mark milestones as claimed + this.claimedMilestones.forEach(pop => { + const milestone = this.milestones.get(pop); + if (milestone) { + milestone.claimed = true; + // Re-apply permanent buffs + this.grantRewards(milestone.rewards); + } + }); + } +} diff --git a/src/systems/NPCSettlementSystem.js b/src/systems/NPCSettlementSystem.js new file mode 100644 index 000000000..4aff1090d --- /dev/null +++ b/src/systems/NPCSettlementSystem.js @@ -0,0 +1,341 @@ +/** + * NPC SETTLEMENT SYSTEM (Magic Helpers) + * NPCs auto-assist with tasks + * Worker efficiency, happiness, housing + */ + +export class NPCSettlementSystem { + constructor(scene) { + this.scene = scene; + + // Settled NPCs + this.settledNPCs = new Map(); + + // Worker assignments + this.assignments = new Map(); // npcId β†’ task + + // NPC stats + this.npcHappiness = new Map(); // npcId β†’ happiness (0-100) + this.npcEfficiency = new Map(); // npcId β†’ efficiency (0.5-2.0) + + // Housing + this.npcHomes = new Map(); // npcId β†’ buildingId + this.housingCapacity = 0; + + // Task types + this.taskTypes = ['farming', 'building', 'crafting', 'defense', 'gathering']; + + this.init(); + } + + init() { + // Update worker tasks periodically + this.scene.time.addEvent({ + delay: 5000, // Every 5s + callback: () => this.updateWorkers(), + loop: true + }); + + // Update happiness hourly + this.scene.time.addEvent({ + delay: 3600000, // 1 hour + callback: () => this.updateHappiness(), + loop: true + }); + } + + /** + * Settle an NPC (they join the town) + */ + settleNPC(npcId, npcData) { + if (this.settledNPCs.has(npcId)) return false; + + //Check housing availability + if (this.settledNPCs.size >= this.housingCapacity) { + console.warn('No housing available'); + return false; + } + + this.settledNPCs.set(npcId, { + id: npcId, + name: npcData.name, + skills: npcData.skills || [], + assignedTask: null, + ...npcData + }); + + // Initial stats + this.npcHappiness.set(npcId, 75); // Start at 75% happiness + this.npcEfficiency.set(npcId, 1.0); // Base efficiency + + // Notification + this.scene.uiSystem?.showNotification( + `${npcData.name} has joined the settlement!`, + 'success', + { icon: 'npc_join' } + ); + + console.log(`🏘️ ${npcData.name} settled in town`); + return true; + } + + /** + * Assign NPC to task + */ + assignTask(npcId, taskType, taskData) { + const npc = this.settledNPCs.get(npcId); + if (!npc) return false; + + // Check if NPC has skill for task + if (!npc.skills.includes(taskType)) { + console.warn(`${npc.name} lacks skill: ${taskType}`); + return false; + } + + // Assign + npc.assignedTask = taskType; + this.assignments.set(npcId, { + type: taskType, + data: taskData, + startTime: Date.now() + }); + + console.log(`πŸ‘· ${npc.name} assigned to ${taskType}`); + return true; + } + + /** + * Unassign NPC from task + */ + unassignTask(npcId) { + const npc = this.settledNPCs.get(npcId); + if (!npc) return false; + + npc.assignedTask = null; + this.assignments.delete(npcId); + + console.log(`${npc.name} unassigned`); + return true; + } + + /** + * Update all worker tasks + */ + updateWorkers() { + this.assignments.forEach((assignment, npcId) => { + const npc = this.settledNPCs.get(npcId); + const efficiency = this.npcEfficiency.get(npcId) || 1.0; + + switch (assignment.type) { + case 'farming': + this.performFarming(npc, efficiency, assignment.data); + break; + case 'building': + this.performBuilding(npc, efficiency, assignment.data); + break; + case 'crafting': + this.performCrafting(npc, efficiency, assignment.data); + break; + case 'defense': + this.performDefense(npc, efficiency); + break; + case 'gathering': + this.performGathering(npc, efficiency); + break; + } + }); + } + + /** + * WORKER TASKS + */ + + performFarming(npc, efficiency, data) { + // Auto-tend crops + const crops = this.scene.crops || []; + const tendsPerCycle = Math.floor(2 * efficiency); + + for (let i = 0; i < tendsPerCycle && i < crops.length; i++) { + const crop = crops[i]; + crop.water?.(); + crop.fertilize?.(); + } + + // Chance to auto-harvest + if (Math.random() < 0.1 * efficiency) { + const harvestable = crops.find(c => c.isHarvestable); + if (harvestable) { + this.scene.farmingSystem?.harvestCrop(harvestable); + console.log(`${npc.name} harvested crop`); + } + } + } + + performBuilding(npc, efficiency, data) { + // Speed up construction + const building = data.buildingId ? this.scene.buildingSystem?.getBuilding(data.buildingId) : null; + + if (building && building.isUnderConstruction) { + const progressBonus = 5 * efficiency; + building.constructionProgress += progressBonus; + + if (building.constructionProgress >= 100) { + this.scene.buildingSystem?.completeConstruction(building.id); + console.log(`${npc.name} completed building: ${building.name}`); + } + } + } + + performCrafting(npc, efficiency, data) { + // Auto-craft items + if (data.recipe) { + const canCraft = this.scene.craftingSystem?.canCraft(data.recipe); + if (canCraft && Math.random() < 0.2 * efficiency) { + this.scene.craftingSystem?.craft(data.recipe); + console.log(`${npc.name} crafted ${data.recipe}`); + } + } + } + + performDefense(npc, efficiency) { + // Patrol and defend + // Increased detection range for raids + this.scene.gameState.buffs.raid_detection_range = (this.scene.gameState.buffs.raid_detection_range || 1.0) + (0.1 * efficiency); + + // Auto-repair walls/defenses + const defenses = this.scene.defenseSystem?.getAllDefenses() || []; + defenses.forEach(defense => { + if (defense.health < defense.maxHealth && Math.random() < 0.05 * efficiency) { + defense.health = Math.min(defense.maxHealth, defense.health + 10); + console.log(`${npc.name} repaired ${defense.name}`); + } + }); + } + + performGathering(npc, efficiency) { + // Auto-gather resources + const gatherChance = 0.15 * efficiency; + + if (Math.random() < gatherChance) { + const resources = ['wood', 'stone', 'berries', 'herbs']; + const resource = Phaser.Utils.Array.GetRandom(resources); + const amount = Math.floor(Phaser.Math.Between(1, 3) * efficiency); + + this.scene.inventorySystem?.addItem(resource, amount); + console.log(`${npc.name} gathered ${amount} ${resource}`); + } + } + + /** + * Update NPC happiness + */ + updateHappiness() { + this.settledNPCs.forEach((npc, npcId) => { + let happiness = this.npcHappiness.get(npcId) || 50; + + // Factors affecting happiness + const hasHome = this.npcHomes.has(npcId); + const hasTask = npc.assignedTask !== null; + const isOverworked = hasTask && this.assignments.get(npcId)?.overworked; + + // Adjustments + if (hasHome) happiness += 5; + if (hasTask) happiness += 2; + if (isOverworked) happiness -= 10; + if (!hasHome && this.settledNPCs.size > this.housingCapacity) happiness -= 15; + + // Natural decay + happiness -= 1; + + // Clamp + happiness = Math.max(0, Math.min(100, happiness)); + this.npcHappiness.set(npcId, happiness); + + // Update efficiency based on happiness + const efficiency = 0.5 + (happiness / 100) * 1.5; // 0.5-2.0 + this.npcEfficiency.set(npcId, efficiency); + + // Low happiness warning + if (happiness < 30) { + this.scene.uiSystem?.showNotification( + `${npc.name} is unhappy!`, + 'warning' + ); + console.warn(`${npc.name} happiness: ${happiness}`); + } + }); + } + + /** + * Assign NPC to home + */ + assignHome(npcId, buildingId) { + this.npcHomes.set(npcId, buildingId); + + // Happiness boost + const happiness = this.npcHappiness.get(npcId) || 50; + this.npcHappiness.set(npcId, Math.min(100, happiness + 20)); + + console.log(`${this.settledNPCs.get(npcId).name} moved into home`); + } + + /** + * Increase housing capacity + */ + addHousing(capacity) { + this.housingCapacity += capacity; + console.log(`Housing capacity: ${this.housingCapacity}`); + } + + /** + * Get settlement statistics + */ + getSettlementStats() { + const npcs = Array.from(this.settledNPCs.values()); + const avgHappiness = npcs.reduce((sum, npc) => sum + (this.npcHappiness.get(npc.id) || 0), 0) / npcs.length || 0; + const avgEfficiency = npcs.reduce((sum, npc) => sum + (this.npcEfficiency.get(npc.id) || 1), 0) / npcs.length || 1; + + return { + population: npcs.length, + housingCapacity: this.housingCapacity, + averageHappiness: Math.round(avgHappiness), + averageEfficiency: avgEfficiency.toFixed(2), + assignedWorkers: this.assignments.size, + unemployed: npcs.length - this.assignments.size + }; + } + + /** + * Get NPCs by skill + */ + getNPCsBySkill(skill) { + return Array.from(this.settledNPCs.values()).filter(npc => npc.skills.includes(skill)); + } + + /** + * Save/Load + */ + getSaveData() { + return { + settledNPCs: Array.from(this.settledNPCs.entries()), + assignments: Array.from(this.assignments.entries()), + npcHappiness: Array.from(this.npcHappiness.entries()), + npcHomes: Array.from(this.npcHomes.entries()), + housingCapacity: this.housingCapacity + }; + } + + loadSaveData(data) { + this.settledNPCs = new Map(data.settledNPCs || []); + this.assignments = new Map(data.assignments || []); + this.npcHappiness = new Map(data.npcHappiness || []); + this.npcHomes = new Map(data.npcHomes || []); + this.housingCapacity = data.housingCapacity || 0; + + // Recalculate efficiency + this.npcHappiness.forEach((happiness, npcId) => { + const efficiency = 0.5 + (happiness / 100) * 1.5; + this.npcEfficiency.set(npcId, efficiency); + }); + } +} diff --git a/src/systems/SchoolBuffSystem.js b/src/systems/SchoolBuffSystem.js new file mode 100644 index 000000000..35abf2715 --- /dev/null +++ b/src/systems/SchoolBuffSystem.js @@ -0,0 +1,316 @@ +/** + * SCHOOL BUFF SYSTEM + * Learning skills from Teacher NPC + * Temporary and permanent buffs + */ + +export class SchoolBuffSystem { + constructor(scene) { + this.scene = scene; + + // Available lessons + this.lessons = new Map(); + this.learnedSkills = new Set(); + + // Active buffs + this.activeBuffs = new Map(); + + // Teacher NPC reference + this.teacher = null; + + this.init(); + } + + init() { + this.initializeLessons(); + } + + /** + * Initialize all available lessons + */ + initializeLessons() { + // FARMING LESSONS + this.registerLesson({ + id: 'basic_farming', + name: 'Basic Farming', + category: 'farming', + cost: 100, + duration: 'permanent', + description: '+10% crop yield', + effect: () => this.scene.gameState.buffs.crop_yield = (this.scene.gameState.buffs.crop_yield || 1.0) * 1.1 + }); + + this.registerLesson({ + id: 'advanced_farming', + name: 'Advanced Farming', + category: 'farming', + cost: 500, + requires: 'basic_farming', + duration: 'permanent', + description: '+20% crop growth speed', + effect: () => this.scene.gameState.buffs.crop_growth_speed = (this.scene.gameState.buffs.crop_growth_speed || 1.0) * 1.2 + }); + + this.registerLesson({ + id: 'fertilizer_mastery', + name: 'Fertilizer Mastery', + category: 'farming', + cost: 300, + duration: 'permanent', + description: 'Fertilizer lasts 2x longer', + effect: () => this.scene.gameState.buffs.fertilizer_duration = 2.0 + }); + + // COMBAT LESSONS + this.registerLesson({ + id: 'basic_combat', + name: 'Basic Combat', + category: 'combat', + cost: 200, + duration: 'permanent', + description: '+5 damage to all attacks', + effect: () => this.scene.player.attackDamage += 5 + }); + + this.registerLesson({ + id: 'defense_training', + name: 'Defense Training', + category: 'combat', + cost: 400, + duration: 'permanent', + description: '+15% damage resistance', + effect: () => this.scene.player.damageResistance = (this.scene.player.damageResistance || 0) + 0.15 + }); + + this.registerLesson({ + id: 'weapon_mastery', + name: 'Weapon Mastery', + category: 'combat', + cost: 800, + requires: 'basic_combat', + duration: 'permanent', + description: '+20% critical hit chance', + effect: () => this.scene.player.critChance = (this.scene.player.critChance || 0) + 0.2 + }); + + // SURVIVAL LESSONS + this.registerLesson({ + id: 'herbalism', + name: 'Herbalism', + category: 'survival', + cost: 250, + duration: 'permanent', + description: 'Healing items restore 50% more HP', + effect: () => this.scene.gameState.buffs.healing_bonus = 1.5 + }); + + this.registerLesson({ + id: 'scavenging', + name: 'Scavenging', + category: 'survival', + cost: 350, + duration: 'permanent', + description: '+25% loot from containers', + effect: () => this.scene.gameState.buffs.loot_bonus = 1.25 + }); + + this.registerLesson({ + id: 'endurance', + name: 'Endurance Training', + category: 'survival', + cost: 600, + duration: 'permanent', + description: '+20 max stamina', + effect: () => this.scene.player.maxStamina += 20 + }); + + // TEMPORARY BUFFS (Study Sessions) + this.registerLesson({ + id: 'focus_boost', + name: 'Focus Boost', + category: 'temporary', + cost: 50, + duration: 300000, // 5 minutes + description: '+50% XP gain for 5 minutes', + effect: () => this.applyTemporaryBuff('xp_boost', 1.5, 300000) + }); + + this.registerLesson({ + id: 'energy_surge', + name: 'Energy Surge', + category: 'temporary', + cost: 75, + duration: 180000, // 3 minutes + description: '+100% stamina regen for 3 minutes', + effect: () => this.applyTemporaryBuff('stamina_regen', 2.0, 180000) + }); + } + + /** + * Register a lesson + */ + registerLesson(lessonData) { + this.lessons.set(lessonData.id, { + ...lessonData, + learned: false + }); + } + + /** + * Learn a skill from Teacher + */ + learnSkill(lessonId) { + const lesson = this.lessons.get(lessonId); + if (!lesson) return false; + + // Check if already learned (permanent skills only) + if (lesson.duration === 'permanent' && lesson.learned) { + console.warn(`Already learned: ${lesson.name}`); + return false; + } + + // Check prerequisites + if (lesson.requires && !this.learnedSkills.has(lesson.requires)) { + const required = this.lessons.get(lesson.requires); + console.warn(`Must learn ${required.name} first`); + return false; + } + + // Check cost + const currency = this.scene.economySystem?.getCurrency() || 0; + if (currency < lesson.cost) { + console.warn(`Insufficient funds: need ${lesson.cost}, have ${currency}`); + return false; + } + + // Pay cost + this.scene.economySystem.addCurrency(-lesson.cost); + + // Apply effect + lesson.effect(); + + // Mark as learned + if (lesson.duration === 'permanent') { + lesson.learned = true; + this.learnedSkills.add(lessonId); + } + + // Notification + this.scene.uiSystem?.showNotification( + `Learned: ${lesson.name}!`, + 'success', + { description: lesson.description } + ); + + // Teacher dialogue + this.scene.dialogueSystem?.startDialogue('teacher', 'lesson_complete', { skill: lesson.name }); + + console.log(`πŸ“š Learned: ${lesson.name} (-${lesson.cost} coins)`); + return true; + } + + /** + * Apply temporary buff + */ + applyTemporaryBuff(buffId, multiplier, duration) { + // Store buff + this.activeBuffs.set(buffId, { + multiplier, + expiresAt: Date.now() + duration + }); + + // VFX + this.scene.vfxSystem?.playEffect('buff_applied', this.scene.player.x, this.scene.player.y); + + // Remove after duration + this.scene.time.delayedCall(duration, () => { + this.activeBuffs.delete(buffId); + this.scene.uiSystem?.showNotification(`${buffId} expired`, 'info'); + console.log(`⏱️ Buff expired: ${buffId}`); + }); + + console.log(`✨ Applied buff: ${buffId} (${duration / 1000}s)`); + } + + /** + * Check if buff is active + */ + hasActiveBuff(buffId) { + const buff = this.activeBuffs.get(buffId); + if (!buff) return false; + + const now = Date.now(); + if (now > buff.expiresAt) { + this.activeBuffs.delete(buffId); + return false; + } + + return true; + } + + /** + * Get buff multiplier + */ + getBuffMultiplier(buffId) { + const buff = this.activeBuffs.get(buffId); + return buff ? buff.multiplier : 1.0; + } + + /** + * Get lessons by category + */ + getLessonsByCategory(category) { + return Array.from(this.lessons.values()).filter(l => l.category === category); + } + + /** + * Get available lessons (can be learned now) + */ + getAvailableLessons() { + return Array.from(this.lessons.values()).filter(l => { + if (l.duration === 'permanent' && l.learned) return false; + if (l.requires && !this.learnedSkills.has(l.requires)) return false; + return true; + }); + } + + /** + * Get learned skills + */ + getLearnedSkills() { + return Array.from(this.lessons.values()).filter(l => l.learned); + } + + /** + * Save/Load + */ + getSaveData() { + return { + learnedSkills: Array.from(this.learnedSkills), + activeBuffs: Array.from(this.activeBuffs.entries()) + }; + } + + loadSaveData(data) { + this.learnedSkills = new Set(data.learnedSkills || []); + + // Mark lessons as learned + this.learnedSkills.forEach(skillId => { + const lesson = this.lessons.get(skillId); + if (lesson) { + lesson.learned = true; + lesson.effect(); // Re-apply permanent effects + } + }); + + // Restore active buffs + if (data.activeBuffs) { + data.activeBuffs.forEach(([buffId, buffData]) => { + const remaining = buffData.expiresAt - Date.now(); + if (remaining > 0) { + this.applyTemporaryBuff(buffId, buffData.multiplier, remaining); + } + }); + } + } +}