📚 DOCUMENTATION COMPLETE: AUDIO_ASSET_MANIFEST.md (61 files detailed) and VFX_IMPLEMENTATION_GUIDE.md (6 systems with code examples). Ready for asset production phase.
This commit is contained in:
219
docs/AUDIO_ASSET_MANIFEST.md
Normal file
219
docs/AUDIO_ASSET_MANIFEST.md
Normal file
@@ -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
|
||||
@@ -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% <EFBFBD> |
|
||||
| **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%** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
357
docs/VFX_IMPLEMENTATION_GUIDE.md
Normal file
357
docs/VFX_IMPLEMENTATION_GUIDE.md
Normal file
@@ -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`
|
||||
179
src/quests/CityGratitudeQuests.js
Normal file
179
src/quests/CityGratitudeQuests.js
Normal file
@@ -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'
|
||||
}
|
||||
};
|
||||
142
src/quests/DefenseQuests.js
Normal file
142
src/quests/DefenseQuests.js
Normal file
@@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
124
src/quests/MuseumQuests.js
Normal file
124
src/quests/MuseumQuests.js
Normal file
@@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
155
src/quests/SchoolQuests.js
Normal file
155
src/quests/SchoolQuests.js
Normal file
@@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
331
src/systems/CityGratitudeSystem.js
Normal file
331
src/systems/CityGratitudeSystem.js
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
341
src/systems/NPCSettlementSystem.js
Normal file
341
src/systems/NPCSettlementSystem.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
316
src/systems/SchoolBuffSystem.js
Normal file
316
src/systems/SchoolBuffSystem.js
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user