From fefc2279b9b02333e13d1f6ac2bcf23a4fc1ee70 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sat, 7 Mar 2026 23:56:59 +0000 Subject: [PATCH 1/2] docs: add World Build System design plans - Add comprehensive neutral mob mechanics design - Include 8-phase implementation plan - Cover alignment system, enemy attraction, aspects, dual-map system - Inspired by Vampire Survivor and Balatro mechanics --- ...nics-dual-map-harvest-and-garden-system.md | 779 ++++++++++++++++++ plans/world-build-system/00-overview.md | 90 ++ .../01-phase-neutral-types.md | 409 +++++++++ .../02-phase-enemy-types.md | 213 +++++ .../03-phase-alignment-effects.md | 439 ++++++++++ .../04-phase-aspect-foundation.md | 340 ++++++++ .../05-phase-aspect-scaling.md | 390 +++++++++ .../world-build-system/06-phase-ecosystem.md | 322 ++++++++ plans/world-build-system/07-phase-dual-map.md | 496 +++++++++++ plans/world-build-system/08-phase-polish.md | 530 ++++++++++++ 10 files changed, 4008 insertions(+) create mode 100644 plans/neutral-mob-mechanics-dual-map-harvest-and-garden-system.md create mode 100644 plans/world-build-system/00-overview.md create mode 100644 plans/world-build-system/01-phase-neutral-types.md create mode 100644 plans/world-build-system/02-phase-enemy-types.md create mode 100644 plans/world-build-system/03-phase-alignment-effects.md create mode 100644 plans/world-build-system/04-phase-aspect-foundation.md create mode 100644 plans/world-build-system/05-phase-aspect-scaling.md create mode 100644 plans/world-build-system/06-phase-ecosystem.md create mode 100644 plans/world-build-system/07-phase-dual-map.md create mode 100644 plans/world-build-system/08-phase-polish.md diff --git a/plans/neutral-mob-mechanics-dual-map-harvest-and-garden-system.md b/plans/neutral-mob-mechanics-dual-map-harvest-and-garden-system.md new file mode 100644 index 0000000..597f766 --- /dev/null +++ b/plans/neutral-mob-mechanics-dual-map-harvest-and-garden-system.md @@ -0,0 +1,779 @@ +# Neutral Mob Mechanics: World Build System + +## Executive Summary + +This plan introduces a **World Build** system inspired by Vampire Survivor's weapon evolutions and Balatro's deck-building synergies. Players terraform the world through harvesting choices, shifting the world's **alignment** between three paths (Arcane, Primal, Forged), which determines which **Aspects** (special abilities) become powerful AND which **enemy types** are attracted to the world. + +Key principles: +- **Fluid Alignment**: Players can shift alignment mid-run, but it takes effort +- **Aspects from Neutrals**: Filling neutrals drops Aspects matching their type +- **Pure vs Hybrid**: Pure builds are high-risk/high-reward; hybrids are safer fallbacks +- **Ecosystem Interactions**: Neutrals near each other create emergent terrain features +- **Enemy Attraction**: Your cultivation choices determine what enemies spawn + +--- + +## Core Design Philosophy + +### Beyond Colored Coins + +Instead of "Resource A buys Thing A," this system creates: +- **Meaningful choices**: What you harvest shapes your entire run +- **Emergent synergies**: Neutral placement and combinations matter +- **Build diversity**: Multiple viable paths through each run +- **Persistent world changes**: Your map evolves visibly +- **Natural challenge scaling**: Your build attracts countering enemies + +### The Balatro Inspiration + +Like Balatro where: +- Your **deck** is terraformed through card additions/removals +- **Jokers** are powerful but need specific deck compositions +- The game rewards building toward synergies + +In this system: +- Your **world** is terraformed through harvesting +- **Aspects** are powerful but need specific world alignments +- **Enemies** adapt to challenge your chosen build +- The game rewards building toward terrain/ability synergies + +--- + +## The Three Alignments + +### Alignment Overview + +| Alignment | Neutral Type | Color | Theme | Playstyle | Attracted Enemies | +|-----------|--------------|-------|-------|-----------|-------------------| +| **Arcane** | Crystal Node | Blue/Cyan | Magic, Energy | Burst damage, mobility | Energy-feeders, magical creatures | +| **Primal** | Seed Pod | Green | Nature, Growth | Sustain, area control | Beasts, insects, nature spirits | +| **Forged** | Ore Vein | Gray/Orange | Industry, Metal | Defense, constructs | Constructs, earth elementals | + +### Alignment Mechanics + +**World Alignment** is tracked as percentages that always sum to 100%: + +``` +Starting State: Arcane 33% | Primal 33% | Forged 34% + +After harvesting 5 Crystals: +Arcane 45% | Primal 28% | Forged 27% + +After harvesting 3 Seeds: +Arcane 40% | Primal 38% | Forged 22% +``` + +**Alignment Shift Formula:** +```javascript +// When harvesting a neutral of type X +alignmentX += harvestValue; +// Normalize so all alignments sum to 100% +total = arcane + primal + forged; +arcane = (arcane / total) * 100; +primal = (primal / total) * 100; +forged = (forged / total) * 100; +``` + +### Alignment Thresholds + +Certain effects activate at alignment thresholds: + +| Threshold | Effect | +|-----------|--------| +| 40% | Minor terrain features appear, enemy type weighting begins | +| 60% | Major terrain features appear, strong enemy type bias | +| 80% | Dominant alignment - powerful effects, matching enemies very common | + +--- + +## Enemy Attraction System + +### Core Concept + +Neutrals are **resources/food sources** that attract specific enemy types. Your cultivation choices determine your opposition. This is thematic/spawn-based - enemies still chase the player, not the neutrals. + +### Thematic Justification + +| Neutral Type | What It Represents | What It Attracts | +|--------------|-------------------|------------------| +| **Crystal Nodes** | Magical energy source | Energy-feeders, magical creatures | +| **Seed Pods** | Life/organic matter | Beasts, insects, nature spirits | +| **Ore Veins** | Metal/mineral deposits | Constructs, earth elementals, scavengers | + +### Enemy Type Categories + +#### Arcane-Attracted Enemies +| Enemy | Behavior | Weakness | Resistance | +|-------|----------|----------|------------| +| **Mana Leech** | Fast, drains player energy | Primal | Arcane | +| **Arcane Wraith** | Phases through terrain | Forged | Arcane | +| **Crystal Golem** | Slow, tanky, reflects magic | Primal | Arcane | +| **Spell Wisp** | Erratic movement, magic projectiles | Forged | Arcane | + +#### Primal-Attracted Enemies +| Enemy | Behavior | Weakness | Resistance | +|-------|----------|----------|------------| +| **Swarm Insects** | Many small enemies | Arcane | Primal | +| **Feral Beast** | Fast, high damage, low health | Forged | Primal | +| **Treant** | Slow, regenerates, area attacks | Arcane | Primal | +| **Spore Cloud** | Poison aura, splits when killed | Forged | Primal | + +#### Forged-Attracted Enemies +| Enemy | Behavior | Weakness | Resistance | +|-------|----------|----------|------------| +| **Rust Crawler** | Corrodes player armor | Primal | Forged | +| **Scrap Golem** | Drops metal on death | Arcane | Forged | +| **Drill Worm** | Burrows, surprise attacks | Primal | Forged | +| **Gear Spider** | Spawns smaller spiders | Arcane | Forged | + +### Spawn Weight System + +Enemy spawns are weighted by current alignment: + +```javascript +// SpawnSystem.js +getEnemySpawnWeights() { + return { + arcaneEnemies: this.alignment.arcane / 100, + primalEnemies: this.alignment.primal / 100, + forgedEnemies: this.alignment.forged / 100 + }; +} + +spawnEnemy() { + const weights = this.getEnemySpawnWeights(); + const roll = Math.random(); + + if (roll < weights.arcaneEnemies) { + return this.spawnArcaneEnemy(); + } else if (roll < weights.arcaneEnemies + weights.primalEnemies) { + return this.spawnPrimalEnemy(); + } else { + return this.spawnForgedEnemy(); + } +} +``` + +### The Strategic Trade-off + +**The Core Tension:** +``` +Pure Arcane Build + ↓ +Strong Arcane Aspects (+100% damage) + ↓ +BUT: 80% of enemies are Arcane-type + ↓ +Arcane enemies RESIST Arcane damage + ↓ +Net effect: Powerful abilities vs resistant enemies +``` + +**Counter-Play Options:** +1. **Overwhelm**: Aspects so strong they overcome resistance +2. **Hybrid Build**: Keep off-alignment Aspects for resistant enemies +3. **Terrain Advantage**: Use terrain features to compensate +4. **Weakness Exploitation**: Arcane enemies are weak to Primal/Forged + +### Enemy Behavior Clarification + +**Important**: Enemies still fundamentally chase the player: +- Enemies spawn because neutrals attracted them to the area +- Once spawned, they chase the player as normal +- Neutrals are NOT attacked by enemies +- The "food source" is flavor/spawn logic, not AI behavior + +--- + +## The Three Neutral Types + +### 1. Crystal Node - Arcane Alignment + +**Visual Design:** +- Translucent blue/cyan crystalline structure +- Internal glow that pulses +- Floating particles orbit around it +- When filled: bright beacon, crackling energy + +**Behavior:** +- Stationary +- Creates small light radius +- Nearby enemies take minor energy damage over time + +**Harvest Effect:** +- +5 Arcane alignment +- Chance to drop Arcane Aspect +- Leaves behind "Ley Point" anchor +- Increases Arcane enemy spawn weight + +**Terrain Influence (at 40%+ Arcane):** +- Energy pools form near harvested crystals +- Ley lines connect nearby Ley Points +- Magical anomalies spawn randomly + +### 2. Seed Pod - Primal Alignment + +**Visual Design:** +- Organic green bulb with leaf protrusions +- Breathing/pulsing animation +- Small roots visible at base +- When filled: opens to reveal glowing seeds + +**Behavior:** +- Slowly drifts in random direction +- Leaves trail of small sprouts +- Nearby vegetation grows faster + +**Harvest Effect:** +- +5 Primal alignment +- Chance to drop Primal Aspect +- Leaves behind "Growth Node" anchor +- Increases Primal enemy spawn weight + +**Terrain Influence (at 40%+ Primal):** +- Vegetation patches spread from Growth Nodes +- Thorny barriers grow naturally +- Healing zones appear in dense vegetation + +### 3. Ore Vein - Forged Alignment + +**Visual Design:** +- Angular metallic rock formation +- Rust-orange and steel-gray coloring +- Metallic sheen reflects light +- When filled: cracks reveal glowing ore inside + +**Behavior:** +- Completely stationary, very heavy +- Enemies bounce off strongly +- Nearby metal objects are magnetized + +**Harvest Effect:** +- +5 Forged alignment +- Chance to drop Forged Aspect +- Leaves behind "Forge Point" anchor +- Increases Forged enemy spawn weight + +**Terrain Influence (at 40%+ Forged):** +- Metal structures emerge from Forge Points +- Automated turret nodes may spawn +- Ground becomes harder, enemies move slower + +--- + +## Ecosystem Interactions + +### Neutral Proximity Effects + +When neutrals of different types are near each other, they create **hybrid terrain features**: + +```mermaid +flowchart TD + subgraph Combinations + C[Crystal] --- S[Seed] + C --- O[Ore] + S --- O + end + + CS[Crystal + Seed = Enchanted Grove] + CO[Crystal + Ore = Power Conduit] + SO[Seed + Ore = Overgrown Ruins] + + C --- CS + S --- CS + C --- CO + O --- CO + S --- SO + O --- SO +``` + +### Hybrid Terrain Features + +#### Enchanted Grove (Crystal + Seed) +- **Appearance**: Glowing plants, bioluminescent flowers +- **Effect**: Healing aura for player, mana regeneration +- **Spawns**: "Living Crystal" hybrid neutrals +- **Enemy Effect**: Attracts hybrid Arcane/Primal enemies + +#### Power Conduit (Crystal + Ore) +- **Appearance**: Electrified metal structures, arcing lightning +- **Effect**: Damages enemies passing through, powers nearby constructs +- **Spawns**: "Charged Ore" hybrid neutrals +- **Enemy Effect**: Attracts hybrid Arcane/Forged enemies + +#### Overgrown Ruins (Seed + Ore) +- **Appearance**: Vines wrapping metal, organic-mechanical fusion +- **Effect**: Creates living walls that regenerate, slows enemies +- **Spawns**: "Ironwood" hybrid neutrals +- **Enemy Effect**: Attracts hybrid Primal/Forged enemies + +### Ecosystem Chain Reactions + +Harvesting can trigger chain effects: + +``` +Harvest Crystal near Seed Pod + ↓ +Seed Pod grows faster - energized + ↓ +When Seed Pod is harvested, drops extra seeds + ↓ +Seeds can be planted in Creation Map +``` + +--- + +## The Aspect System + +### What Are Aspects? + +Aspects are **special abilities** that become more powerful based on world alignment. Like Balatro's Jokers, they reward building toward specific synergies. + +### Aspect Acquisition + +**Primary Source: Neutral Harvesting** +- Each filled neutral has a chance to drop an Aspect +- Aspect type matches neutral type (Crystal to Arcane Aspect) +- Higher fill level = higher drop chance + +**Secondary Sources:** +- Boss defeats +- Milestone rewards (survive X minutes) +- Rare terrain features + +### Aspect Categories + +#### Arcane Aspects (Blue) + +| Aspect | Effect | Alignment Bonus | +|--------|--------|-----------------| +| **Mana Surge** | +25% spell damage | +50% at 60% Arcane | +| **Ley Walker** | Teleport between Ley Points | Range +100% at 60% Arcane | +| **Crystal Resonance** | Nearby crystals amplify attacks | +1 crystal range per 10% Arcane | +| **Energy Shield** | Absorb damage as mana | Shield strength scales with Arcane% | +| **Chain Lightning** | Attacks arc to nearby enemies | +1 arc per 20% Arcane | + +#### Primal Aspects (Green) + +| Aspect | Effect | Alignment Bonus | +|--------|--------|-----------------| +| **Overgrowth** | Enemies in vegetation take DoT | DoT +50% at 60% Primal | +| **Seed Scatter** | Attacks plant obstacle seeds | Seed count scales with Primal% | +| **Regeneration** | Passive health recovery | +100% regen at 60% Primal | +| **Beast Companion** | Summon animal ally | Companion strength scales with Primal% | +| **Root Snare** | Enemies near vegetation are slowed | Slow +25% per 20% Primal | + +#### Forged Aspects (Orange/Gray) + +| Aspect | Effect | Alignment Bonus | +|--------|--------|-----------------| +| **Magnetize** | Pull enemies toward ore | Pull strength +50% at 60% Forged | +| **Construct Ally** | Ore deposits spawn turret drones | Drone damage scales with Forged% | +| **Metal Skin** | Passive armor | +100% armor at 60% Forged | +| **Salvage** | Enemies drop scrap for temporary buffs | Scrap value scales with Forged% | +| **Overclock** | Turrets fire faster | +5% fire rate per 10% Forged | + +#### Hybrid Aspects (Require Mixed Alignment) + +| Aspect | Requirement | Effect | +|--------|-------------|--------| +| **Living Steel** | 40% Primal + 40% Forged | Armor regenerates over time | +| **Storm Caller** | 40% Arcane + 40% Primal | Lightning spreads through vegetation | +| **Arcane Forge** | 40% Arcane + 40% Forged | Constructs gain energy shields | +| **Elemental Mastery** | 30% each | All abilities gain minor bonuses | +| **World Shaper** | 50% any + 25% each other | Terrain features are stronger | + +### Aspect Slots + +Players have limited Aspect slots: +- Start with 3 slots +- Can unlock up to 6 slots through progression +- Must choose which Aspects to keep +- Discarded Aspects can be found again + +--- + +## Build Archetypes + +### Pure Builds (High Risk, High Reward) + +#### The Archmage (80%+ Arcane) +``` +Focus: Crystal harvesting exclusively +World State: Magical wasteland, energy everywhere +Enemy Population: 80% Arcane-type - resistant to your damage +Strengths: + - Devastating burst damage + - High mobility via Ley Walker + - Energy shields for defense +Weaknesses: + - Enemies resist Arcane damage + - Must rely on terrain/raw power to overcome +Recommended Aspects: + - Mana Surge + - Chain Lightning + - Energy Shield + - Ley Walker +``` + +#### The Druid (80%+ Primal) +``` +Focus: Seed harvesting exclusively +World State: Overgrown jungle, life everywhere +Enemy Population: 80% Primal-type - resistant to your damage +Strengths: + - Incredible sustain via regeneration + - Area denial through vegetation + - Beast companions for damage +Weaknesses: + - Enemies resist Primal damage + - Must out-sustain rather than burst +Recommended Aspects: + - Regeneration + - Overgrowth + - Beast Companion + - Root Snare +``` + +#### The Engineer (80%+ Forged) +``` +Focus: Ore harvesting exclusively +World State: Industrial fortress, metal everywhere +Enemy Population: 80% Forged-type - resistant to your damage +Strengths: + - Incredible defense via armor + - Automated turrets for damage + - Enemies slowed on metal terrain +Weaknesses: + - Enemies resist Forged damage + - Must rely on constructs and terrain +Recommended Aspects: + - Metal Skin + - Construct Ally + - Overclock + - Magnetize +``` + +### Hybrid Builds (Safer, More Versatile) + +#### The Battle Mage (Arcane + Forged) +``` +Focus: Crystals and Ore, avoid Seeds +World State: Electrified metal structures +Enemy Mix: 40% Arcane, 40% Forged, 20% Primal +Advantage: Primal enemies are weak to both your types +Recommended Aspects: + - Arcane Forge + - Energy Shield + - Construct Ally +``` + +#### The Shaman (Arcane + Primal) +``` +Focus: Crystals and Seeds, avoid Ore +World State: Enchanted groves, magical nature +Enemy Mix: 40% Arcane, 40% Primal, 20% Forged +Advantage: Forged enemies are weak to both your types +Recommended Aspects: + - Storm Caller + - Regeneration + - Chain Lightning +``` + +#### The Warden (Primal + Forged) +``` +Focus: Seeds and Ore, avoid Crystals +World State: Overgrown ruins, living walls +Enemy Mix: 40% Primal, 40% Forged, 20% Arcane +Advantage: Arcane enemies are weak to both your types +Recommended Aspects: + - Living Steel + - Overgrowth + - Metal Skin +``` + +--- + +## Dual Map System + +### PvE Map (Combat Mode) + +**Purpose:** Fight enemies, fill and harvest neutrals, experience terraformed world + +**Features:** +- Enemies actively spawn and attack (types based on alignment) +- Neutrals appear based on world state +- Terrain features are active (damage, slow, heal) +- Aspects are active +- Time flows normally + +**Player Actions:** +- Move and dodge enemies +- Auto-attack fills nearest neutral/damages enemies +- Harvest filled neutrals (walk over them) +- Use Aspect abilities + +### Creation Map (Garden Mode) + +**Purpose:** Plan strategy, place seeds, view alignment, manage Aspects + +**Features:** +- No enemies (peaceful) +- See alignment percentages and thresholds +- View terrain feature locations +- Manage Aspect loadout +- Preview enemy spawn weights +- Time paused or heavily slowed + +**Player Actions:** +- Plant seeds to influence neutral spawns +- View and equip/unequip Aspects +- See predicted terrain evolution +- Plan harvest routes +- View enemy type distribution + +### Map Switching + +**How to Switch:** +- Dedicated key/button (e.g., Tab or M) +- Brief transition effect (0.3 seconds) +- Player position preserved + +--- + +## Gameplay Flow + +### Early Game (0-2 minutes) + +**World State:** Neutral, balanced alignment, mixed enemy types +**Player Goal:** Explore, find neutrals, start building toward a path +**Decisions:** +- Which neutral type to prioritize? +- What Aspects have I found? +- What enemy types am I seeing? + +### Mid Game (2-5 minutes) + +**World State:** Alignment shifting, terrain features appearing, enemy types biasing +**Player Goal:** Commit to a build path, synergize Aspects with alignment +**Decisions:** +- Double down on current alignment or diversify? +- Am I handling the resistant enemies well? +- Which Aspects to keep vs discard? + +### Late Game (5+ minutes) + +**World State:** Strongly aligned, powerful terrain effects, dominant enemy type +**Player Goal:** Maximize synergies, survive escalating difficulty +**Decisions:** +- Maintain alignment or shift for hybrid Aspects? +- Use terrain features to handle resistant enemies +- Optimize Aspect loadout for current world state + +--- + +## Technical Implementation + +### New Systems Required + +``` +vampire-survivor/js/ +├── systems/ +│ ├── AlignmentSystem.js # Track and update world alignment +│ ├── TerrainSystem.js # Manage terrain features +│ ├── AspectSystem.js # Handle Aspect effects and bonuses +│ ├── EcosystemSystem.js # Neutral proximity interactions +│ ├── EnemySpawnSystem.js # Alignment-weighted enemy spawning +│ └── MapManager.js # Dual map switching +├── entities/ +│ ├── neutrals/ +│ │ ├── CrystalNode.js +│ │ ├── SeedPod.js +│ │ └── OreVein.js +│ ├── enemies/ +│ │ ├── arcane/ # Arcane-attracted enemies +│ │ ├── primal/ # Primal-attracted enemies +│ │ └── forged/ # Forged-attracted enemies +│ ├── terrain/ +│ │ ├── EnergyPool.js +│ │ ├── VegetationPatch.js +│ │ ├── MetalPlate.js +│ │ └── HybridTerrain.js +│ └── aspects/ +│ ├── ArcaneAspects.js +│ ├── PrimalAspects.js +│ ├── ForgedAspects.js +│ └── HybridAspects.js +└── config/ + ├── AlignmentConfig.js # Thresholds, scaling values + ├── TerrainConfig.js # Terrain feature definitions + ├── AspectConfig.js # Aspect definitions and bonuses + └── EnemyConfig.js # Enemy type definitions and resistances +``` + +### GameState Additions + +```javascript +// Add to GameState.js +this.alignment = { + arcane: 33.33, + primal: 33.33, + forged: 33.34 +}; + +this.aspects = { + equipped: [], // Currently active Aspects + inventory: [], // Collected but not equipped + maxSlots: 3 // Can be upgraded +}; + +this.terrain = { + features: [], // Active terrain features + leyPoints: [], // Crystal harvest locations + growthNodes: [], // Seed harvest locations + forgePoints: [] // Ore harvest locations +}; + +this.enemySpawnWeights = { + arcane: 0.33, + primal: 0.33, + forged: 0.34 +}; + +this.currentMap = 'pve'; // 'pve' or 'creation' +``` + +--- + +## Implementation Phases + +### Phase 1: Multiple Neutral Types +- Create CrystalNode, SeedPod, OreVein classes +- Different visuals and behaviors +- Basic harvesting with alignment tracking +- UI showing current alignment percentages + +### Phase 2: Enemy Type System +- Create enemy categories (Arcane, Primal, Forged) +- Implement 2-3 enemies per category +- Add resistance/weakness system +- Alignment-weighted spawn system + +### Phase 3: Alignment Effects +- Implement alignment thresholds +- Basic terrain features at 40%+ +- Visual world changes based on dominant alignment +- Enemy spawn weights update with alignment + +### Phase 4: Aspect System Foundation +- Create Aspect base class +- Implement 3-4 Aspects per alignment type +- Aspect drops from neutral harvesting +- Basic Aspect equip/unequip UI + +### Phase 5: Aspect Scaling +- Aspects scale with alignment percentage +- Implement alignment bonus calculations +- Visual feedback for powered-up Aspects +- Balance pass on Aspect power levels + +### Phase 6: Ecosystem Interactions +- Neutral proximity detection +- Hybrid terrain feature spawning +- Chain reaction effects +- Hybrid neutral types + +### Phase 7: Dual Map System +- Creation Map implementation +- Map switching mechanic +- Seed planting in Creation Map +- Aspect management UI +- Enemy distribution preview + +### Phase 8: Hybrid Aspects and Polish +- Implement hybrid Aspects +- Balance pure vs hybrid builds +- Visual polish for all terrain types +- Audio feedback for alignment shifts + +--- + +## Balance Considerations + +### Pure vs Hybrid Balance + +**Pure Builds (80%+ single alignment):** +- +100% bonus to matching Aspects +- Powerful terrain features +- BUT: 80% of enemies resist your damage type +- High risk, high reward + +**Hybrid Builds (40-60% two alignments):** +- +50% bonus to both matching Aspects +- Access to hybrid Aspects +- Enemy mix is more manageable +- Safer, more versatile + +**Generalist (30-40% each):** +- +25% bonus to all Aspects +- Access to Elemental Mastery +- Balanced enemy distribution +- Safest, but no powerful synergies + +### Enemy Resistance Values + +| Damage Type | vs Resistant | vs Neutral | vs Weak | +|-------------|--------------|------------|---------| +| Arcane | 50% damage | 100% damage | 150% damage | +| Primal | 50% damage | 100% damage | 150% damage | +| Forged | 50% damage | 100% damage | 150% damage | + +### Aspect Drop Rates + +- Base drop rate: 20% per harvest +- Scales with fill level: 100% fill = 30% drop rate +- Aspect type matches neutral type 80% of time +- 20% chance for random type (enables hybrid builds) + +--- + +## Open Questions + +1. **Boss Enemies**: Should there be alignment-specific bosses? + +2. **Alignment Decay**: Should alignment slowly drift toward neutral over time? + +3. **Meta Progression**: Should there be unlockable Aspects between runs? + +4. **Neutral Spawning**: Should alignment affect which neutrals spawn more? + +5. **Enemy Scaling**: Should enemy health/damage scale with alignment purity? + +--- + +## Success Metrics + +The system is working when: + +- Players feel their harvesting choices matter +- Different builds feel meaningfully different +- Enemy resistance creates interesting challenge, not frustration +- Hybrid builds are viable alternatives to pure builds +- Terrain features create strategic opportunities +- Aspects create exciting power spikes +- The world visibly transforms based on player choices +- Replayability is high due to build diversity + +--- + +## Conclusion + +This World Build system transforms neutral mobs from simple "armored treasure chests" into a deep strategic layer that defines each run. By combining: + +- **Terraforming**: Your harvests physically change the world +- **Alignment**: Your choices shift the world toward different themes +- **Enemy Attraction**: Your cultivation determines your opposition +- **Ecosystem**: Neutrals interact to create emergent features +- **Aspects**: Powerful abilities that reward building toward synergies + +Players experience a Balatro-like satisfaction of building toward powerful combinations, while facing natural challenge scaling through the enemy attraction system. The vampire-survivor core gameplay of survival and harvesting remains intact, enhanced by meaningful strategic depth. diff --git a/plans/world-build-system/00-overview.md b/plans/world-build-system/00-overview.md new file mode 100644 index 0000000..eed588e --- /dev/null +++ b/plans/world-build-system/00-overview.md @@ -0,0 +1,90 @@ +# World Build System - Overview + +## Executive Summary + +This system transforms neutral mobs from simple "armored treasure chests" into a deep strategic layer that defines each run. Inspired by Vampire Survivor's weapon evolutions and Balatro's deck-building synergies. + +## Core Concept + +Players terraform the world through harvesting choices, shifting the world's **alignment** between three paths (Arcane, Primal, Forged), which determines: +- Which **Aspects** (special abilities) become powerful +- Which **enemy types** are attracted to the world +- What **terrain features** appear + +## The Three Alignments + +| Alignment | Neutral Type | Color | Theme | Playstyle | +|-----------|--------------|-------|-------|-----------| +| **Arcane** | Crystal Node | Blue/Cyan | Magic, Energy | Burst damage, mobility | +| **Primal** | Seed Pod | Green | Nature, Growth | Sustain, area control | +| **Forged** | Ore Vein | Gray/Orange | Industry, Metal | Defense, constructs | + +## Key Systems + +### 1. Alignment System +- World alignment tracked as percentages (always sum to 100%) +- Harvesting shifts alignment toward that neutral's type +- Thresholds at 40%, 60%, 80% trigger effects + +### 2. Enemy Attraction +- Neutrals conceptually attract specific enemy types +- Your cultivation determines your opposition +- Enemies resist damage matching their attraction type +- Creates natural challenge scaling + +### 3. Terrain Terraforming +- Alignment affects what terrain features spawn +- Neutral proximity creates hybrid terrain +- Terrain provides strategic advantages + +### 4. Aspect System +- Powerful abilities dropped from neutrals +- Scale with matching alignment percentage +- Pure builds: high risk, high reward +- Hybrid builds: safer, more versatile + +### 5. Dual Map System +- PvE Map: Combat, harvest, experience world +- Creation Map: Plan, plant seeds, manage Aspects + +## Build Archetypes + +### Pure Builds (80%+ single alignment) +- **The Archmage**: Devastating magic, high mobility +- **The Druid**: Incredible sustain, area control +- **The Engineer**: Strong defense, automated turrets + +### Hybrid Builds (40%+ two alignments) +- **Battle Mage**: Arcane + Forged +- **Shaman**: Arcane + Primal +- **Warden**: Primal + Forged + +## Implementation Phases + +1. **Phase 1**: Multiple Neutral Types +2. **Phase 2**: Enemy Type System +3. **Phase 3**: Alignment Effects +4. **Phase 4**: Aspect System Foundation +5. **Phase 5**: Aspect Scaling +6. **Phase 6**: Ecosystem Interactions +7. **Phase 7**: Dual Map System +8. **Phase 8**: Hybrid Aspects and Polish + +## Success Metrics + +- Players feel harvesting choices matter +- Different builds feel meaningfully different +- Enemy resistance creates interesting challenge +- Terrain features create strategic opportunities +- High replayability due to build diversity + +## Related Documents + +- [Phase 1: Multiple Neutral Types](./01-phase-neutral-types.md) +- [Phase 2: Enemy Type System](./02-phase-enemy-types.md) +- [Phase 3: Alignment Effects](./03-phase-alignment-effects.md) +- [Phase 4: Aspect System Foundation](./04-phase-aspect-foundation.md) +- [Phase 5: Aspect Scaling](./05-phase-aspect-scaling.md) +- [Phase 6: Ecosystem Interactions](./06-phase-ecosystem.md) +- [Phase 7: Dual Map System](./07-phase-dual-map.md) +- [Phase 8: Polish and Balance](./08-phase-polish.md) diff --git a/plans/world-build-system/01-phase-neutral-types.md b/plans/world-build-system/01-phase-neutral-types.md new file mode 100644 index 0000000..a15fac4 --- /dev/null +++ b/plans/world-build-system/01-phase-neutral-types.md @@ -0,0 +1,409 @@ +# Phase 1: Multiple Neutral Types + +## Overview + +Create three distinct neutral entity types that form the foundation of the alignment system. Each type has unique visuals, behaviors, and harvest rewards. + +## Goals + +- [ ] Refactor existing NeutralEntity into base class +- [ ] Create CrystalNode class (Arcane alignment) +- [ ] Create SeedPod class (Primal alignment) +- [ ] Create OreVein class (Forged alignment) +- [ ] Implement basic alignment tracking +- [ ] Add alignment UI display +- [ ] Update spawner to spawn different types + +## The Three Neutral Types + +### 1. Crystal Node (Arcane) + +**Visual Design:** +- Translucent blue/cyan crystalline structure +- Internal glow that pulses (sine wave animation) +- Floating particles orbit around it +- When filled: bright beacon effect, crackling energy particles + +**Behavior:** +- Stationary (mass: 100, immovable) +- Creates small light radius (visual only for now) +- Fill level shown as internal glow intensity + +**Code Structure:** +```javascript +// js/entities/neutrals/CrystalNode.js +import { NeutralEntity } from './NeutralEntity.js'; + +export class CrystalNode extends NeutralEntity { + constructor(x, y) { + super(x, y, 35); + this.type = 'crystal'; + this.alignmentType = 'arcane'; + this.color = '#00aaff'; + this.glowColor = '#00ddff'; + this.harvestReward = { type: 'arcane', amount: 5 }; + + // Visual properties + this.glowIntensity = 0; + this.particleAngle = 0; + this.pulsePhase = 0; + } + + update(deltaTime) { + super.update(deltaTime); + + // Pulsing glow based on fill level + this.pulsePhase += deltaTime * 2; + this.glowIntensity = (this.fillLevel / this.maxFill) * + (0.5 + 0.5 * Math.sin(this.pulsePhase)); + + // Orbiting particles + this.particleAngle += deltaTime * 1.5; + } + + draw(ctx, cameraX, cameraY) { + // Draw glow effect + // Draw crystal shape (hexagonal or diamond) + // Draw orbiting particles + // Draw fill indicator + } +} +``` + +### 2. Seed Pod (Primal) + +**Visual Design:** +- Organic green bulb with leaf protrusions +- Breathing/pulsing animation (scale oscillation) +- Small roots visible at base +- When filled: opens slightly, revealing glowing seeds + +**Behavior:** +- Slowly drifts in random direction (speed: 10 pixels/sec) +- Leaves trail of small sprout particles +- Changes drift direction periodically + +**Code Structure:** +```javascript +// js/entities/neutrals/SeedPod.js +import { NeutralEntity } from './NeutralEntity.js'; + +export class SeedPod extends NeutralEntity { + constructor(x, y) { + super(x, y, 30); + this.type = 'seed'; + this.alignmentType = 'primal'; + this.color = '#44aa44'; + this.glowColor = '#66ff66'; + this.harvestReward = { type: 'primal', amount: 5 }; + + // Movement properties + this.driftSpeed = 10; + this.driftAngle = Math.random() * Math.PI * 2; + this.driftChangeTimer = 0; + this.driftChangeInterval = 3; // seconds + + // Visual properties + this.breathePhase = 0; + this.breatheScale = 1; + } + + update(deltaTime) { + super.update(deltaTime); + + // Breathing animation + this.breathePhase += deltaTime * 1.5; + this.breatheScale = 1 + 0.1 * Math.sin(this.breathePhase); + + // Drift movement + this.driftChangeTimer += deltaTime; + if (this.driftChangeTimer >= this.driftChangeInterval) { + this.driftAngle += (Math.random() - 0.5) * Math.PI; + this.driftChangeTimer = 0; + } + + this.x += Math.cos(this.driftAngle) * this.driftSpeed * deltaTime; + this.y += Math.sin(this.driftAngle) * this.driftSpeed * deltaTime; + } + + draw(ctx, cameraX, cameraY) { + // Draw with breathe scale + // Draw bulb shape + // Draw leaf protrusions + // Draw roots + // Draw fill indicator (seeds visible inside) + } +} +``` + +### 3. Ore Vein (Forged) + +**Visual Design:** +- Angular metallic rock formation +- Rust-orange and steel-gray coloring +- Metallic sheen (specular highlight) +- When filled: cracks appear revealing glowing ore inside + +**Behavior:** +- Completely stationary +- Very heavy (mass: 200) - enemies bounce off strongly +- No animation except crack progression + +**Code Structure:** +```javascript +// js/entities/neutrals/OreVein.js +import { NeutralEntity } from './NeutralEntity.js'; + +export class OreVein extends NeutralEntity { + constructor(x, y) { + super(x, y, 45); + this.type = 'ore'; + this.alignmentType = 'forged'; + this.color = '#888899'; + this.glowColor = '#ffaa44'; + this.harvestReward = { type: 'forged', amount: 5 }; + this.mass = 200; // Extra heavy + + // Visual properties + this.crackLevel = 0; // 0-1 based on fill + this.shimmerPhase = 0; + } + + update(deltaTime) { + super.update(deltaTime); + + // Update crack level based on fill + this.crackLevel = this.fillLevel / this.maxFill; + + // Subtle shimmer on metallic surface + this.shimmerPhase += deltaTime * 0.5; + } + + draw(ctx, cameraX, cameraY) { + // Draw rock formation (angular polygon) + // Draw metallic sheen + // Draw cracks based on crackLevel + // Draw glowing ore in cracks when filled + } +} +``` + +## Base Class Refactor + +```javascript +// js/entities/neutrals/NeutralEntity.js +import { BaseEntity } from '../BaseEntity.js'; + +export class NeutralEntity extends BaseEntity { + constructor(x, y, size) { + super(x, y, size); + this.type = 'neutral'; // Override in subclass + this.alignmentType = null; // 'arcane', 'primal', 'forged' + this.fillLevel = 0; + this.maxFill = 10; + this.targetable = true; + this.harvestReward = { type: null, amount: 0 }; + + // Physics + this.vx = 0; + this.vy = 0; + this.mass = 100; + } + + takeDamage(damage) { + if (!this.targetable) return false; + + this.fillLevel += damage; + if (this.fillLevel >= this.maxFill) { + this.fillLevel = this.maxFill; + this.targetable = false; + return true; // Ready to harvest + } + return false; + } + + harvest() { + // Returns the harvest reward + // Called when player walks over filled neutral + return this.harvestReward; + } + + update(deltaTime) { + // Base update - override in subclasses + } + + draw(ctx, cameraX, cameraY) { + // Base draw - override in subclasses + } +} +``` + +## Alignment Tracking + +```javascript +// js/systems/AlignmentSystem.js +export class AlignmentSystem { + constructor(gameState) { + this.gameState = gameState; + this.alignment = { + arcane: 33.33, + primal: 33.33, + forged: 33.34 + }; + } + + harvestNeutral(neutralType) { + const harvestValue = 5; + + switch(neutralType) { + case 'arcane': + this.alignment.arcane += harvestValue; + break; + case 'primal': + this.alignment.primal += harvestValue; + break; + case 'forged': + this.alignment.forged += harvestValue; + break; + } + + this.normalizeAlignment(); + return this.alignment; + } + + normalizeAlignment() { + const total = this.alignment.arcane + + this.alignment.primal + + this.alignment.forged; + + this.alignment.arcane = (this.alignment.arcane / total) * 100; + this.alignment.primal = (this.alignment.primal / total) * 100; + this.alignment.forged = (this.alignment.forged / total) * 100; + } + + getAlignment() { + return { ...this.alignment }; + } + + getDominantAlignment() { + const { arcane, primal, forged } = this.alignment; + if (arcane >= primal && arcane >= forged) return 'arcane'; + if (primal >= arcane && primal >= forged) return 'primal'; + return 'forged'; + } +} +``` + +## Spawner Updates + +```javascript +// Updates to js/game/Spawner.js +import { CrystalNode } from '../entities/neutrals/CrystalNode.js'; +import { SeedPod } from '../entities/neutrals/SeedPod.js'; +import { OreVein } from '../entities/neutrals/OreVein.js'; + +// In spawnNeutral method: +spawnNeutral(game) { + // Random type selection (equal probability for now) + const roll = Math.random(); + let neutral; + + const spawnPos = this.getSpawnPosition(game); + + if (roll < 0.33) { + neutral = new CrystalNode(spawnPos.x, spawnPos.y); + } else if (roll < 0.66) { + neutral = new SeedPod(spawnPos.x, spawnPos.y); + } else { + neutral = new OreVein(spawnPos.x, spawnPos.y); + } + + game.neutralEntities.push(neutral); +} +``` + +## UI Display + +Add alignment display to the info panel: + +```javascript +// In Renderer.js or a new UI component +drawAlignmentUI(ctx, alignment) { + const x = 10; + const y = 150; + const barWidth = 150; + const barHeight = 20; + + // Background + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(x, y, barWidth, barHeight); + + // Arcane segment (blue) + ctx.fillStyle = '#00aaff'; + ctx.fillRect(x, y, barWidth * (alignment.arcane / 100), barHeight); + + // Primal segment (green) + ctx.fillStyle = '#44aa44'; + const primalStart = barWidth * (alignment.arcane / 100); + ctx.fillRect(x + primalStart, y, barWidth * (alignment.primal / 100), barHeight); + + // Forged segment (orange) + ctx.fillStyle = '#ff8844'; + const forgedStart = primalStart + barWidth * (alignment.primal / 100); + ctx.fillRect(x + forgedStart, y, barWidth * (alignment.forged / 100), barHeight); + + // Labels + ctx.fillStyle = '#ffffff'; + ctx.font = '12px Arial'; + ctx.fillText(`A: ${alignment.arcane.toFixed(0)}%`, x + 5, y + 14); + ctx.fillText(`P: ${alignment.primal.toFixed(0)}%`, x + 55, y + 14); + ctx.fillText(`F: ${alignment.forged.toFixed(0)}%`, x + 105, y + 14); +} +``` + +## File Structure After Phase 1 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── BaseEntity.js +│ ├── Enemy.js +│ ├── HeavyEnemy.js +│ ├── Projectile.js +│ ├── WeaponPickup.js +│ └── neutrals/ +│ ├── NeutralEntity.js # Refactored base class +│ ├── CrystalNode.js # NEW +│ ├── SeedPod.js # NEW +│ └── OreVein.js # NEW +├── systems/ +│ └── AlignmentSystem.js # NEW +├── game/ +│ ├── CollisionSystem.js +│ ├── GameState.js # Add alignment state +│ ├── Renderer.js # Add alignment UI +│ ├── Spawner.js # Update for neutral types +│ └── TargetingSystem.js +└── config/ + └── CollisionConfig.js +``` + +## Testing Checklist + +- [ ] All three neutral types spawn correctly +- [ ] Each type has distinct visual appearance +- [ ] Crystal Node: stationary, pulsing glow +- [ ] Seed Pod: drifts slowly, breathing animation +- [ ] Ore Vein: stationary, heavy, cracks when filled +- [ ] Harvesting updates alignment percentages +- [ ] Alignment UI displays correctly +- [ ] Alignment always sums to 100% +- [ ] Collision physics work for all types + +## Dependencies + +- None (this is the foundation phase) + +## Next Phase + +[Phase 2: Enemy Type System](./02-phase-enemy-types.md) - Create alignment-specific enemy types with resistance/weakness system. diff --git a/plans/world-build-system/02-phase-enemy-types.md b/plans/world-build-system/02-phase-enemy-types.md new file mode 100644 index 0000000..ef83ca4 --- /dev/null +++ b/plans/world-build-system/02-phase-enemy-types.md @@ -0,0 +1,213 @@ +# Phase 2: Enemy Type System + +## Overview + +Create three categories of enemies that are attracted by different alignment types. Each enemy type has resistances and weaknesses, creating strategic depth in build choices. + +## Goals + +- [ ] Create enemy base class with damage type support +- [ ] Implement Arcane-attracted enemies (2-3 types) +- [ ] Implement Primal-attracted enemies (2-3 types) +- [ ] Implement Forged-attracted enemies (2-3 types) +- [ ] Add resistance/weakness damage calculation +- [ ] Implement alignment-weighted spawn system +- [ ] Update UI to show enemy type distribution + +## Damage Type System + +### Resistance/Weakness Values + +| Damage vs Enemy | Resistant | Neutral | Weak | +|-----------------|-----------|---------|------| +| Multiplier | 0.5x | 1.0x | 1.5x | + +### Type Relationships + +``` +Arcane enemies: Resist Arcane, Weak to Primal & Forged +Primal enemies: Resist Primal, Weak to Arcane & Forged +Forged enemies: Resist Forged, Weak to Arcane & Primal +``` + +## Enemy Base Class Update + +```javascript +// js/entities/enemies/EnemyBase.js +import { BaseEntity } from '../BaseEntity.js'; + +export class EnemyBase extends BaseEntity { + constructor(x, y, size) { + super(x, y, size); + this.enemyType = 'neutral'; // 'arcane', 'primal', 'forged' + this.health = 3; + this.maxHealth = 3; + this.speed = 150; + this.damage = 1; + + // Damage resistances + this.resistances = { + arcane: 1.0, + primal: 1.0, + forged: 1.0 + }; + } + + takeDamage(amount, damageType = 'neutral') { + const multiplier = this.resistances[damageType] || 1.0; + const actualDamage = amount * multiplier; + this.health -= actualDamage; + + // Visual feedback for resistance/weakness + if (multiplier < 1.0) { + this.showResistEffect(); + } else if (multiplier > 1.0) { + this.showWeaknessEffect(); + } + + return this.health <= 0; + } + + showResistEffect() { + // Flash with shield icon or "RESIST" text + } + + showWeaknessEffect() { + // Flash brighter, "WEAK" text, extra particles + } +} +``` + +## Arcane-Attracted Enemies + +### Mana Leech +- **Size**: 20 (small) +- **Health**: 2 +- **Speed**: 180 (fast) +- **Behavior**: Erratic jittery movement +- **Visual**: Purple/blue wispy creature with trailing particles + +### Arcane Wraith +- **Size**: 30 (medium) +- **Health**: 4 +- **Speed**: 120 (medium) +- **Behavior**: Periodically phases (becomes semi-transparent) +- **Visual**: Ghostly figure, fades in and out + +## Primal-Attracted Enemies + +### Swarm Insect +- **Size**: 12 (very small) +- **Health**: 1 +- **Speed**: 200 (very fast) +- **Behavior**: Moves in swarm patterns with offset from player +- **Visual**: Small bug/insect, spawns in groups + +### Feral Beast +- **Size**: 35 (medium-large) +- **Health**: 3 +- **Speed**: 170 (fast), 350 when charging +- **Behavior**: Charges at player when close +- **Visual**: Wolf/beast shape, glows red when charging + +## Forged-Attracted Enemies + +### Rust Crawler +- **Size**: 25 (medium) +- **Health**: 4 +- **Speed**: 100 (slow) +- **Behavior**: Steady movement, leaves rust trail +- **Visual**: Mechanical spider/crawler, rust-colored + +### Scrap Golem +- **Size**: 50 (large) +- **Health**: 8 +- **Speed**: 80 (very slow) +- **Behavior**: Slow but tanky, high damage +- **Visual**: Large construct made of scrap metal + +## Alignment-Weighted Spawn System + +```javascript +// js/systems/EnemySpawnSystem.js +export class EnemySpawnSystem { + constructor(alignmentSystem) { + this.alignmentSystem = alignmentSystem; + this.enemyTypes = { + arcane: [ManaLeech, ArcaneWraith], + primal: [SwarmInsect, FeralBeast], + forged: [RustCrawler, ScrapGolem] + }; + } + + getSpawnWeights() { + const alignment = this.alignmentSystem.getAlignment(); + return { + arcane: alignment.arcane / 100, + primal: alignment.primal / 100, + forged: alignment.forged / 100 + }; + } + + spawnEnemy(x, y) { + const weights = this.getSpawnWeights(); + const roll = Math.random(); + + let category; + if (roll < weights.arcane) { + category = 'arcane'; + } else if (roll < weights.arcane + weights.primal) { + category = 'primal'; + } else { + category = 'forged'; + } + + // Random enemy from category + const types = this.enemyTypes[category]; + const EnemyClass = types[Math.floor(Math.random() * types.length)]; + + return new EnemyClass(x, y); + } +} +``` + +## File Structure After Phase 2 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── enemies/ +│ │ ├── EnemyBase.js # NEW - base class with resistances +│ │ ├── arcane/ +│ │ │ ├── ManaLeech.js # NEW +│ │ │ └── ArcaneWraith.js # NEW +│ │ ├── primal/ +│ │ │ ├── SwarmInsect.js # NEW +│ │ │ └── FeralBeast.js # NEW +│ │ └── forged/ +│ │ ├── RustCrawler.js # NEW +│ │ └── ScrapGolem.js # NEW +│ └── neutrals/ +│ └── ... +├── systems/ +│ ├── AlignmentSystem.js +│ └── EnemySpawnSystem.js # NEW +└── ... +``` + +## Testing Checklist + +- [ ] All enemy types spawn correctly +- [ ] Each enemy has distinct visual appearance +- [ ] Resistance/weakness damage multipliers work +- [ ] Visual feedback shows for resist/weak hits +- [ ] Spawn weights match alignment percentages +- [ ] Enemy behaviors work as designed + +## Dependencies + +- Phase 1: Multiple Neutral Types (for alignment tracking) + +## Next Phase + +[Phase 3: Alignment Effects](./03-phase-alignment-effects.md) - Implement terrain features and world changes based on alignment thresholds. diff --git a/plans/world-build-system/03-phase-alignment-effects.md b/plans/world-build-system/03-phase-alignment-effects.md new file mode 100644 index 0000000..d8fe2b3 --- /dev/null +++ b/plans/world-build-system/03-phase-alignment-effects.md @@ -0,0 +1,439 @@ +# Phase 3: Alignment Effects + +## Overview + +Implement terrain features and visual world changes that appear based on alignment thresholds. The world should visibly transform as players commit to an alignment path. + +## Goals + +- [ ] Implement alignment threshold system (40%, 60%, 80%) +- [ ] Create Arcane terrain features (energy pools, ley lines) +- [ ] Create Primal terrain features (vegetation, thorns) +- [ ] Create Forged terrain features (metal plates, turret nodes) +- [ ] Add visual world tinting based on dominant alignment +- [ ] Implement anchor point system (Ley Points, Growth Nodes, Forge Points) + +## Alignment Thresholds + +| Threshold | Effect | +|-----------|--------| +| 40% | Minor terrain features begin appearing | +| 60% | Major terrain features, strong visual shift | +| 80% | Dominant alignment, maximum effects | + +## Terrain Feature Base Class + +```javascript +// js/entities/terrain/TerrainFeature.js +export class TerrainFeature { + constructor(x, y, radius) { + this.x = x; + this.y = y; + this.radius = radius; + this.active = true; + this.alignmentType = 'neutral'; + } + + isEntityInRange(entity) { + const dx = entity.x - this.x; + const dy = entity.y - this.y; + return Math.sqrt(dx * dx + dy * dy) <= this.radius; + } + + applyEffect(entity, deltaTime) { + // Override in subclasses + } + + update(deltaTime) { + // Override in subclasses + } + + draw(ctx, cameraX, cameraY) { + // Override in subclasses + } +} +``` + +## Arcane Terrain Features + +### Energy Pool +- **Appearance**: Glowing blue circle on ground +- **Effect**: Damages enemies standing in it (2 DPS) +- **Spawns**: Near Ley Points when Arcane > 40% + +```javascript +// js/entities/terrain/arcane/EnergyPool.js +export class EnergyPool extends TerrainFeature { + constructor(x, y) { + super(x, y, 60); + this.alignmentType = 'arcane'; + this.damagePerSecond = 2; + this.pulsePhase = 0; + } + + applyEffect(entity, deltaTime) { + if (entity.enemyType && this.isEntityInRange(entity)) { + entity.takeDamage(this.damagePerSecond * deltaTime, 'arcane'); + } + } + + draw(ctx, cameraX, cameraY) { + const screenX = this.x - cameraX; + const screenY = this.y - cameraY; + + // Pulsing glow effect + this.pulsePhase += 0.05; + const alpha = 0.3 + 0.2 * Math.sin(this.pulsePhase); + + ctx.beginPath(); + ctx.arc(screenX, screenY, this.radius, 0, Math.PI * 2); + ctx.fillStyle = `rgba(0, 170, 255, ${alpha})`; + ctx.fill(); + } +} +``` + +### Ley Line +- **Appearance**: Glowing line connecting two Ley Points +- **Effect**: Boosts Arcane damage for player in area (+25%) +- **Spawns**: Automatically connects nearby Ley Points + +```javascript +// js/entities/terrain/arcane/LeyLine.js +export class LeyLine extends TerrainFeature { + constructor(point1, point2) { + const midX = (point1.x + point2.x) / 2; + const midY = (point1.y + point2.y) / 2; + super(midX, midY, 30); // Width of effect area + + this.point1 = point1; + this.point2 = point2; + this.alignmentType = 'arcane'; + this.damageBoost = 0.25; + } + + isEntityInRange(entity) { + // Check if entity is near the line segment + return this.distanceToLine(entity.x, entity.y) <= this.radius; + } + + distanceToLine(px, py) { + // Calculate perpendicular distance to line segment + const dx = this.point2.x - this.point1.x; + const dy = this.point2.y - this.point1.y; + const length = Math.sqrt(dx * dx + dy * dy); + + if (length === 0) return Math.sqrt((px - this.point1.x) ** 2 + (py - this.point1.y) ** 2); + + const t = Math.max(0, Math.min(1, ((px - this.point1.x) * dx + (py - this.point1.y) * dy) / (length * length))); + const nearestX = this.point1.x + t * dx; + const nearestY = this.point1.y + t * dy; + + return Math.sqrt((px - nearestX) ** 2 + (py - nearestY) ** 2); + } + + draw(ctx, cameraX, cameraY) { + const x1 = this.point1.x - cameraX; + const y1 = this.point1.y - cameraY; + const x2 = this.point2.x - cameraX; + const y2 = this.point2.y - cameraY; + + // Glowing line + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.strokeStyle = 'rgba(0, 200, 255, 0.6)'; + ctx.lineWidth = 8; + ctx.stroke(); + + // Inner bright line + ctx.strokeStyle = 'rgba(150, 230, 255, 0.8)'; + ctx.lineWidth = 3; + ctx.stroke(); + } +} +``` + +## Primal Terrain Features + +### Vegetation Patch +- **Appearance**: Green grass/plant area +- **Effect**: Heals player (1 HP/sec), slows enemies by 20% +- **Spawns**: Near Growth Nodes when Primal > 40% + +```javascript +// js/entities/terrain/primal/VegetationPatch.js +export class VegetationPatch extends TerrainFeature { + constructor(x, y) { + super(x, y, 80); + this.alignmentType = 'primal'; + this.healPerSecond = 1; + this.slowAmount = 0.2; + this.swayPhase = Math.random() * Math.PI * 2; + } + + applyEffect(entity, deltaTime) { + if (this.isEntityInRange(entity)) { + if (entity.isPlayer) { + entity.heal(this.healPerSecond * deltaTime); + } else if (entity.enemyType) { + entity.speedMultiplier = 1 - this.slowAmount; + } + } + } + + draw(ctx, cameraX, cameraY) { + const screenX = this.x - cameraX; + const screenY = this.y - cameraY; + + // Draw grass patch + ctx.beginPath(); + ctx.arc(screenX, screenY, this.radius, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(60, 140, 60, 0.4)'; + ctx.fill(); + + // Draw individual grass blades (simplified) + this.swayPhase += 0.02; + // ... grass blade drawing + } +} +``` + +### Thorn Barrier +- **Appearance**: Thorny vine wall +- **Effect**: Damages enemies passing through (3 damage) +- **Spawns**: At edges of vegetation areas when Primal > 60% + +## Forged Terrain Features + +### Metal Plate +- **Appearance**: Industrial metal floor section +- **Effect**: Slows enemies by 30%, player moves normally +- **Spawns**: Near Forge Points when Forged > 40% + +```javascript +// js/entities/terrain/forged/MetalPlate.js +export class MetalPlate extends TerrainFeature { + constructor(x, y) { + super(x, y, 70); + this.alignmentType = 'forged'; + this.enemySlowAmount = 0.3; + } + + applyEffect(entity, deltaTime) { + if (this.isEntityInRange(entity) && entity.enemyType) { + entity.speedMultiplier = 1 - this.enemySlowAmount; + } + } + + draw(ctx, cameraX, cameraY) { + const screenX = this.x - cameraX; + const screenY = this.y - cameraY; + + // Draw metal plate (hexagonal or rectangular) + ctx.fillStyle = 'rgba(100, 100, 110, 0.6)'; + ctx.fillRect(screenX - this.radius, screenY - this.radius, + this.radius * 2, this.radius * 2); + + // Metallic highlights + ctx.strokeStyle = 'rgba(150, 150, 160, 0.8)'; + ctx.lineWidth = 2; + ctx.strokeRect(screenX - this.radius, screenY - this.radius, + this.radius * 2, this.radius * 2); + } +} +``` + +### Turret Node +- **Appearance**: Small automated turret +- **Effect**: Auto-attacks nearby enemies (1 damage/sec) +- **Spawns**: Near Forge Points when Forged > 60% + +## Anchor Point System + +When neutrals are harvested, they leave behind anchor points: + +```javascript +// js/systems/AnchorPointSystem.js +export class AnchorPointSystem { + constructor() { + this.leyPoints = []; // From Crystal harvests + this.growthNodes = []; // From Seed harvests + this.forgePoints = []; // From Ore harvests + } + + addAnchorPoint(type, x, y) { + const point = { x, y, createdAt: Date.now() }; + + switch(type) { + case 'arcane': + this.leyPoints.push(point); + break; + case 'primal': + this.growthNodes.push(point); + break; + case 'forged': + this.forgePoints.push(point); + break; + } + } + + getPointsOfType(type) { + switch(type) { + case 'arcane': return this.leyPoints; + case 'primal': return this.growthNodes; + case 'forged': return this.forgePoints; + default: return []; + } + } +} +``` + +## Terrain System + +```javascript +// js/systems/TerrainSystem.js +export class TerrainSystem { + constructor(alignmentSystem, anchorPointSystem) { + this.alignmentSystem = alignmentSystem; + this.anchorPointSystem = anchorPointSystem; + this.terrainFeatures = []; + this.spawnCheckInterval = 2; // seconds + this.timeSinceLastCheck = 0; + } + + update(deltaTime, entities) { + // Check for new terrain spawns + this.timeSinceLastCheck += deltaTime; + if (this.timeSinceLastCheck >= this.spawnCheckInterval) { + this.checkTerrainSpawns(); + this.timeSinceLastCheck = 0; + } + + // Update existing terrain + for (const terrain of this.terrainFeatures) { + terrain.update(deltaTime); + + // Apply effects to entities + for (const entity of entities) { + terrain.applyEffect(entity, deltaTime); + } + } + } + + checkTerrainSpawns() { + const alignment = this.alignmentSystem.getAlignment(); + + // Arcane terrain at 40%+ + if (alignment.arcane >= 40) { + this.trySpawnArcaneFeatures(); + } + + // Primal terrain at 40%+ + if (alignment.primal >= 40) { + this.trySpawnPrimalFeatures(); + } + + // Forged terrain at 40%+ + if (alignment.forged >= 40) { + this.trySpawnForgedFeatures(); + } + } + + trySpawnArcaneFeatures() { + const leyPoints = this.anchorPointSystem.getPointsOfType('arcane'); + + // Spawn energy pools near ley points + for (const point of leyPoints) { + if (Math.random() < 0.1 && !this.hasFeatureNear(point.x, point.y, 100)) { + this.terrainFeatures.push(new EnergyPool( + point.x + (Math.random() - 0.5) * 100, + point.y + (Math.random() - 0.5) * 100 + )); + } + } + + // Connect nearby ley points with ley lines + this.tryCreateLeyLines(leyPoints); + } + + hasFeatureNear(x, y, distance) { + return this.terrainFeatures.some(f => { + const dx = f.x - x; + const dy = f.y - y; + return Math.sqrt(dx * dx + dy * dy) < distance; + }); + } +} +``` + +## World Visual Tinting + +```javascript +// In Renderer.js +drawWorldTint(ctx, alignment, canvasWidth, canvasHeight) { + const dominant = this.getDominantAlignment(alignment); + let tintColor; + let tintAlpha = 0; + + // Calculate tint based on dominant alignment + if (alignment.arcane >= 40) { + tintColor = '0, 100, 200'; + tintAlpha = (alignment.arcane - 40) / 100 * 0.15; + } else if (alignment.primal >= 40) { + tintColor = '50, 150, 50'; + tintAlpha = (alignment.primal - 40) / 100 * 0.15; + } else if (alignment.forged >= 40) { + tintColor = '150, 100, 50'; + tintAlpha = (alignment.forged - 40) / 100 * 0.15; + } + + if (tintAlpha > 0) { + ctx.fillStyle = `rgba(${tintColor}, ${tintAlpha})`; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + } +} +``` + +## File Structure After Phase 3 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── terrain/ +│ │ ├── TerrainFeature.js # NEW - base class +│ │ ├── arcane/ +│ │ │ ├── EnergyPool.js # NEW +│ │ │ └── LeyLine.js # NEW +│ │ ├── primal/ +│ │ │ ├── VegetationPatch.js # NEW +│ │ │ └── ThornBarrier.js # NEW +│ │ └── forged/ +│ │ ├── MetalPlate.js # NEW +│ │ └── TurretNode.js # NEW +├── systems/ +│ ├── AlignmentSystem.js +│ ├── EnemySpawnSystem.js +│ ├── AnchorPointSystem.js # NEW +│ └── TerrainSystem.js # NEW +└── ... +``` + +## Testing Checklist + +- [ ] Terrain features spawn at correct alignment thresholds +- [ ] Energy pools damage enemies +- [ ] Ley lines connect nearby Ley Points +- [ ] Vegetation patches heal player and slow enemies +- [ ] Metal plates slow enemies +- [ ] World tint changes with dominant alignment +- [ ] Anchor points created on neutral harvest + +## Dependencies + +- Phase 1: Multiple Neutral Types +- Phase 2: Enemy Type System + +## Next Phase + +[Phase 4: Aspect System Foundation](./04-phase-aspect-foundation.md) - Create the Aspect ability system with basic abilities for each alignment. diff --git a/plans/world-build-system/04-phase-aspect-foundation.md b/plans/world-build-system/04-phase-aspect-foundation.md new file mode 100644 index 0000000..814b100 --- /dev/null +++ b/plans/world-build-system/04-phase-aspect-foundation.md @@ -0,0 +1,340 @@ +# Phase 4: Aspect System Foundation + +## Overview + +Create the Aspect ability system - powerful abilities that drop from neutrals and scale with world alignment. This phase focuses on the foundation: base class, 3-4 aspects per alignment, and basic UI. + +## Goals + +- [ ] Create Aspect base class +- [ ] Implement 3-4 Arcane Aspects +- [ ] Implement 3-4 Primal Aspects +- [ ] Implement 3-4 Forged Aspects +- [ ] Add Aspect drop system to neutral harvesting +- [ ] Create Aspect inventory and equip system +- [ ] Build Aspect UI (equipped slots, inventory) + +## Aspect Base Class + +```javascript +// js/entities/aspects/Aspect.js +export class Aspect { + constructor() { + this.id = ''; + this.name = ''; + this.description = ''; + this.alignmentType = 'neutral'; // 'arcane', 'primal', 'forged' + this.rarity = 'common'; // 'common', 'uncommon', 'rare' + + // Effect values (base, before alignment scaling) + this.baseEffect = {}; + + // State + this.equipped = false; + this.cooldown = 0; + this.maxCooldown = 0; + } + + // Calculate effect with alignment bonus + getScaledEffect(alignmentSystem) { + const alignment = alignmentSystem.getAlignment(); + const matchingAlignment = alignment[this.alignmentType] || 33; + + // Base scaling: 1.0 at 33%, up to 2.0 at 100% + const scale = 1 + (matchingAlignment - 33) / 67; + + const scaledEffect = {}; + for (const [key, value] of Object.entries(this.baseEffect)) { + scaledEffect[key] = value * scale; + } + + return scaledEffect; + } + + update(deltaTime, gameState, alignmentSystem) { + if (this.cooldown > 0) { + this.cooldown -= deltaTime; + } + } + + applyPassive(player, alignmentSystem) { + // Override in subclasses + } + + activate(player, gameState, alignmentSystem) { + // Override in subclasses + } + + canActivate() { + return this.cooldown <= 0; + } +} +``` + +## Arcane Aspects + +### Mana Surge +- **Effect**: +25% Arcane damage (scales to +50% at 100% Arcane) +- **Type**: Passive + +### Chain Lightning +- **Effect**: Attacks arc to 2 nearby enemies (scales to 4 at 100%) +- **Type**: Passive (triggers on hit) + +### Energy Shield +- **Effect**: 20 point damage shield, regenerates when not hit +- **Type**: Passive + +### Ley Walker +- **Effect**: Teleport to nearest Ley Point (active ability) +- **Type**: Active, 10 second cooldown +- **Rarity**: Rare + +## Primal Aspects + +### Regeneration +- **Effect**: +1 HP/sec passive healing (scales to +2 at 100%) +- **Type**: Passive + +### Overgrowth +- **Effect**: Enemies in vegetation take 2 DPS (scales to 4) +- **Type**: Passive (enhances terrain) + +### Beast Companion +- **Effect**: Summon wolf ally that attacks enemies +- **Type**: Passive (always active) + +### Root Snare +- **Effect**: Enemies near vegetation slowed 25% (scales to 50%) +- **Type**: Passive (enhances terrain) + +## Forged Aspects + +### Metal Skin +- **Effect**: +20% damage reduction (scales to +40%) +- **Type**: Passive + +### Construct Ally +- **Effect**: Forge Points spawn turret drones +- **Type**: Passive + +### Magnetize +- **Effect**: Pull enemies toward ore deposits +- **Type**: Active, 8 second cooldown + +### Overclock +- **Effect**: Turrets fire 25% faster (scales to 50%) +- **Type**: Passive (enhances terrain) + +## Aspect Drop System + +```javascript +// js/systems/AspectDropSystem.js +export class AspectDropSystem { + constructor() { + this.aspectPool = { + arcane: [ManaSurge, ChainLightning, EnergyShield, LeyWalker], + primal: [Regeneration, Overgrowth, BeastCompanion, RootSnare], + forged: [MetalSkin, ConstructAlly, Magnetize, Overclock] + }; + + this.baseDropRate = 0.20; // 20% base chance + } + + rollForAspect(neutralType, fillLevel, maxFill) { + // Higher fill = higher drop chance + const fillBonus = (fillLevel / maxFill) * 0.10; + const dropChance = this.baseDropRate + fillBonus; + + if (Math.random() > dropChance) { + return null; // No drop + } + + // 80% chance matching type, 20% random + let aspectType = neutralType; + if (Math.random() > 0.80) { + const types = ['arcane', 'primal', 'forged']; + aspectType = types[Math.floor(Math.random() * types.length)]; + } + + // Pick random aspect from pool + const pool = this.aspectPool[aspectType]; + const AspectClass = pool[Math.floor(Math.random() * pool.length)]; + + return new AspectClass(); + } +} +``` + +## Aspect Manager + +```javascript +// js/systems/AspectManager.js +export class AspectManager { + constructor() { + this.equipped = []; // Currently equipped aspects + this.inventory = []; // Collected but not equipped + this.maxSlots = 3; // Starting slots + this.maxInventory = 10; + } + + addAspect(aspect) { + if (this.inventory.length < this.maxInventory) { + this.inventory.push(aspect); + return true; + } + return false; // Inventory full + } + + equipAspect(aspect) { + if (this.equipped.length >= this.maxSlots) { + return false; // No slots available + } + + const index = this.inventory.indexOf(aspect); + if (index > -1) { + this.inventory.splice(index, 1); + this.equipped.push(aspect); + aspect.equipped = true; + return true; + } + return false; + } + + unequipAspect(aspect) { + const index = this.equipped.indexOf(aspect); + if (index > -1) { + this.equipped.splice(index, 1); + this.inventory.push(aspect); + aspect.equipped = false; + return true; + } + return false; + } + + update(deltaTime, gameState, alignmentSystem) { + for (const aspect of this.equipped) { + aspect.update(deltaTime, gameState, alignmentSystem); + aspect.applyPassive(gameState.player, alignmentSystem); + } + } + + getEquippedOfType(alignmentType) { + return this.equipped.filter(a => a.alignmentType === alignmentType); + } +} +``` + +## Aspect UI + +```javascript +// js/ui/AspectUI.js +export class AspectUI { + constructor(aspectManager) { + this.aspectManager = aspectManager; + this.slotSize = 50; + this.padding = 10; + } + + draw(ctx, canvasWidth, canvasHeight, alignmentSystem) { + // Draw equipped slots at bottom of screen + const startX = canvasWidth / 2 - (this.aspectManager.maxSlots * (this.slotSize + this.padding)) / 2; + const y = canvasHeight - this.slotSize - 20; + + for (let i = 0; i < this.aspectManager.maxSlots; i++) { + const x = startX + i * (this.slotSize + this.padding); + + // Draw slot background + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(x, y, this.slotSize, this.slotSize); + ctx.strokeStyle = '#666'; + ctx.strokeRect(x, y, this.slotSize, this.slotSize); + + // Draw aspect if equipped + const aspect = this.aspectManager.equipped[i]; + if (aspect) { + this.drawAspectIcon(ctx, aspect, x, y, alignmentSystem); + } + } + } + + drawAspectIcon(ctx, aspect, x, y, alignmentSystem) { + // Color based on alignment type + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + + ctx.fillStyle = colors[aspect.alignmentType] || '#888'; + ctx.fillRect(x + 5, y + 5, this.slotSize - 10, this.slotSize - 10); + + // Show cooldown overlay if on cooldown + if (aspect.cooldown > 0) { + const cooldownPercent = aspect.cooldown / aspect.maxCooldown; + ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; + ctx.fillRect(x + 5, y + 5, this.slotSize - 10, (this.slotSize - 10) * cooldownPercent); + } + + // Show alignment scaling indicator + const effect = aspect.getScaledEffect(alignmentSystem); + const scale = Object.values(effect)[0] / Object.values(aspect.baseEffect)[0]; + if (scale > 1.2) { + ctx.strokeStyle = '#ffff00'; + ctx.lineWidth = 2; + ctx.strokeRect(x + 3, y + 3, this.slotSize - 6, this.slotSize - 6); + } + } +} +``` + +## File Structure After Phase 4 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── aspects/ +│ │ ├── Aspect.js # NEW - base class +│ │ ├── arcane/ +│ │ │ ├── ManaSurge.js # NEW +│ │ │ ├── ChainLightning.js # NEW +│ │ │ ├── EnergyShield.js # NEW +│ │ │ └── LeyWalker.js # NEW +│ │ ├── primal/ +│ │ │ ├── Regeneration.js # NEW +│ │ │ ├── Overgrowth.js # NEW +│ │ │ ├── BeastCompanion.js # NEW +│ │ │ └── RootSnare.js # NEW +│ │ └── forged/ +│ │ ├── MetalSkin.js # NEW +│ │ ├── ConstructAlly.js # NEW +│ │ ├── Magnetize.js # NEW +│ │ └── Overclock.js # NEW +├── systems/ +│ ├── AspectDropSystem.js # NEW +│ └── AspectManager.js # NEW +├── ui/ +│ └── AspectUI.js # NEW +└── ... +``` + +## Testing Checklist + +- [ ] Aspects drop from harvested neutrals +- [ ] Drop rate scales with fill level +- [ ] Aspects can be equipped/unequipped +- [ ] Passive effects apply correctly +- [ ] Active abilities trigger and go on cooldown +- [ ] UI shows equipped aspects +- [ ] UI shows cooldown state +- [ ] Alignment scaling works correctly + +## Dependencies + +- Phase 1: Multiple Neutral Types +- Phase 2: Enemy Type System +- Phase 3: Alignment Effects + +## Next Phase + +[Phase 5: Aspect Scaling](./05-phase-aspect-scaling.md) - Implement detailed alignment scaling and visual feedback for powered-up aspects. diff --git a/plans/world-build-system/05-phase-aspect-scaling.md b/plans/world-build-system/05-phase-aspect-scaling.md new file mode 100644 index 0000000..15d5a8b --- /dev/null +++ b/plans/world-build-system/05-phase-aspect-scaling.md @@ -0,0 +1,390 @@ +# Phase 5: Aspect Scaling + +## Overview + +Implement detailed alignment scaling for Aspects, including visual feedback when aspects are powered up, threshold bonuses, and the pure build bonus system. + +## Goals + +- [ ] Implement tiered scaling based on alignment thresholds +- [ ] Add visual feedback for powered-up aspects +- [ ] Create pure build bonus (80%+ alignment) +- [ ] Add penalty system for off-alignment aspects +- [ ] Implement aspect power indicators in UI +- [ ] Add floating damage numbers showing bonus damage + +## Scaling Tiers + +| Alignment % | Matching Aspect Bonus | Off-Type Penalty | +|-------------|----------------------|------------------| +| 0-33% | 1.0x (base) | 1.0x | +| 34-59% | 1.25x | 0.9x | +| 60-79% | 1.5x | 0.75x | +| 80-100% | 2.0x (pure build) | 0.5x | + +## Enhanced Scaling System + +```javascript +// js/systems/AspectScalingSystem.js +export class AspectScalingSystem { + constructor(alignmentSystem) { + this.alignmentSystem = alignmentSystem; + + this.scalingTiers = [ + { threshold: 80, matchBonus: 2.0, offPenalty: 0.5 }, + { threshold: 60, matchBonus: 1.5, offPenalty: 0.75 }, + { threshold: 34, matchBonus: 1.25, offPenalty: 0.9 }, + { threshold: 0, matchBonus: 1.0, offPenalty: 1.0 } + ]; + } + + getScaleForAspect(aspect) { + const alignment = this.alignmentSystem.getAlignment(); + const aspectType = aspect.alignmentType; + const matchingPercent = alignment[aspectType] || 33; + + // Find applicable tier + const tier = this.scalingTiers.find(t => matchingPercent >= t.threshold); + + return { + multiplier: tier.matchBonus, + tier: this.getTierName(tier.threshold), + isPureBuild: tier.threshold >= 80 + }; + } + + getOffTypeScale(aspectType) { + const alignment = this.alignmentSystem.getAlignment(); + + // Check if any alignment is dominant enough to penalize off-types + for (const [type, percent] of Object.entries(alignment)) { + if (type !== aspectType && percent >= 60) { + const tier = this.scalingTiers.find(t => percent >= t.threshold); + return tier.offPenalty; + } + } + + return 1.0; // No penalty + } + + getTierName(threshold) { + switch(threshold) { + case 80: return 'pure'; + case 60: return 'major'; + case 34: return 'minor'; + default: return 'base'; + } + } +} +``` + +## Visual Feedback System + +### Aspect Glow Effects + +```javascript +// js/ui/AspectVisualEffects.js +export class AspectVisualEffects { + constructor() { + this.glowColors = { + base: 'rgba(255, 255, 255, 0)', + minor: 'rgba(255, 255, 100, 0.3)', + major: 'rgba(255, 200, 50, 0.5)', + pure: 'rgba(255, 150, 0, 0.8)' + }; + + this.pulsePhase = 0; + } + + update(deltaTime) { + this.pulsePhase += deltaTime * 3; + } + + drawAspectGlow(ctx, x, y, size, tier) { + const baseColor = this.glowColors[tier]; + if (tier === 'base') return; + + // Pulsing glow + const pulse = 0.7 + 0.3 * Math.sin(this.pulsePhase); + + ctx.save(); + ctx.shadowColor = baseColor; + ctx.shadowBlur = 15 * pulse; + ctx.fillStyle = baseColor; + ctx.fillRect(x, y, size, size); + ctx.restore(); + + // Pure build special effect + if (tier === 'pure') { + this.drawPureBuildEffect(ctx, x, y, size); + } + } + + drawPureBuildEffect(ctx, x, y, size) { + // Particle ring around aspect + const centerX = x + size / 2; + const centerY = y + size / 2; + const radius = size * 0.8; + + for (let i = 0; i < 8; i++) { + const angle = (i / 8) * Math.PI * 2 + this.pulsePhase * 0.5; + const px = centerX + Math.cos(angle) * radius; + const py = centerY + Math.sin(angle) * radius; + + ctx.beginPath(); + ctx.arc(px, py, 3, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255, 200, 100, 0.8)'; + ctx.fill(); + } + } +} +``` + +### Floating Damage Numbers + +```javascript +// js/ui/FloatingNumbers.js +export class FloatingNumbers { + constructor() { + this.numbers = []; + } + + addNumber(x, y, value, type = 'normal') { + const colors = { + normal: '#ffffff', + bonus: '#ffff00', + critical: '#ff4444', + resist: '#888888', + weak: '#00ff00' + }; + + this.numbers.push({ + x, y, + value: Math.round(value * 10) / 10, + color: colors[type], + scale: type === 'critical' ? 1.5 : 1.0, + lifetime: 1.0, + vy: -50 + }); + } + + update(deltaTime) { + for (let i = this.numbers.length - 1; i >= 0; i--) { + const num = this.numbers[i]; + num.y += num.vy * deltaTime; + num.lifetime -= deltaTime; + + if (num.lifetime <= 0) { + this.numbers.splice(i, 1); + } + } + } + + draw(ctx, cameraX, cameraY) { + for (const num of this.numbers) { + const screenX = num.x - cameraX; + const screenY = num.y - cameraY; + const alpha = Math.min(1, num.lifetime * 2); + + ctx.save(); + ctx.globalAlpha = alpha; + ctx.font = `bold ${16 * num.scale}px Arial`; + ctx.fillStyle = num.color; + ctx.textAlign = 'center'; + ctx.fillText(num.value.toString(), screenX, screenY); + ctx.restore(); + } + } +} +``` + +## Power Indicator UI + +```javascript +// js/ui/PowerIndicator.js +export class PowerIndicator { + constructor() { + this.indicators = { + arcane: { current: 1.0, target: 1.0 }, + primal: { current: 1.0, target: 1.0 }, + forged: { current: 1.0, target: 1.0 } + }; + } + + update(deltaTime, scalingSystem) { + // Smoothly animate toward target values + for (const type of ['arcane', 'primal', 'forged']) { + const scale = scalingSystem.getScaleForAspect({ alignmentType: type }); + this.indicators[type].target = scale.multiplier; + + const diff = this.indicators[type].target - this.indicators[type].current; + this.indicators[type].current += diff * deltaTime * 3; + } + } + + draw(ctx, x, y) { + const barWidth = 100; + const barHeight = 8; + const spacing = 12; + + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + + let offsetY = 0; + for (const [type, indicator] of Object.entries(this.indicators)) { + // Background + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(x, y + offsetY, barWidth, barHeight); + + // Fill based on multiplier (1.0 = 50%, 2.0 = 100%) + const fillPercent = (indicator.current - 0.5) / 1.5; + ctx.fillStyle = colors[type]; + ctx.fillRect(x, y + offsetY, barWidth * fillPercent, barHeight); + + // Label + ctx.fillStyle = '#ffffff'; + ctx.font = '10px Arial'; + ctx.fillText(`${type[0].toUpperCase()}: ${indicator.current.toFixed(1)}x`, + x + barWidth + 5, y + offsetY + 7); + + offsetY += spacing; + } + } +} +``` + +## Enhanced Aspect Base Class + +```javascript +// Updates to Aspect.js +getScaledEffect(scalingSystem) { + const scale = scalingSystem.getScaleForAspect(this); + const offTypeScale = scalingSystem.getOffTypeScale(this.alignmentType); + + const finalMultiplier = scale.multiplier * offTypeScale; + + const scaledEffect = {}; + for (const [key, value] of Object.entries(this.baseEffect)) { + scaledEffect[key] = value * finalMultiplier; + } + + return { + ...scaledEffect, + tier: scale.tier, + isPureBuild: scale.isPureBuild, + multiplier: finalMultiplier + }; +} + +// Visual indicator of power level +getPowerLevel(scalingSystem) { + const scale = scalingSystem.getScaleForAspect(this); + return { + tier: scale.tier, + multiplier: scale.multiplier, + color: this.getTierColor(scale.tier) + }; +} + +getTierColor(tier) { + const colors = { + base: '#888888', + minor: '#aaaaaa', + major: '#ffcc00', + pure: '#ff8800' + }; + return colors[tier]; +} +``` + +## Pure Build Notification + +```javascript +// js/ui/PureBuildNotification.js +export class PureBuildNotification { + constructor() { + this.active = false; + this.alignmentType = null; + this.animationPhase = 0; + this.displayTime = 3; + } + + trigger(alignmentType) { + this.active = true; + this.alignmentType = alignmentType; + this.animationPhase = 0; + this.displayTime = 3; + } + + update(deltaTime) { + if (!this.active) return; + + this.animationPhase += deltaTime; + this.displayTime -= deltaTime; + + if (this.displayTime <= 0) { + this.active = false; + } + } + + draw(ctx, canvasWidth, canvasHeight) { + if (!this.active) return; + + const centerX = canvasWidth / 2; + const centerY = canvasHeight / 3; + + // Fade in/out + const alpha = this.displayTime > 2.5 ? + (3 - this.displayTime) * 2 : + Math.min(1, this.displayTime / 0.5); + + ctx.save(); + ctx.globalAlpha = alpha; + + // Glow effect + ctx.shadowColor = this.getTypeColor(); + ctx.shadowBlur = 20 + 10 * Math.sin(this.animationPhase * 5); + + // Text + ctx.font = 'bold 36px Arial'; + ctx.fillStyle = this.getTypeColor(); + ctx.textAlign = 'center'; + ctx.fillText('PURE BUILD ACHIEVED', centerX, centerY); + + ctx.font = '24px Arial'; + ctx.fillText(`${this.alignmentType.toUpperCase()} MASTERY`, centerX, centerY + 40); + + ctx.restore(); + } + + getTypeColor() { + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + return colors[this.alignmentType] || '#ffffff'; + } +} +``` + +## Testing Checklist + +- [ ] Aspects scale correctly at each threshold +- [ ] Off-type penalties apply when alignment is high +- [ ] Visual glow effects show power level +- [ ] Pure build notification triggers at 80% +- [ ] Floating damage numbers show bonus damage +- [ ] Power indicator UI updates smoothly +- [ ] Aspect UI shows tier coloring + +## Dependencies + +- Phase 1-4 (all previous phases) + +## Next Phase + +[Phase 6: Ecosystem Interactions](./06-phase-ecosystem.md) - Implement neutral proximity effects and hybrid terrain features. diff --git a/plans/world-build-system/06-phase-ecosystem.md b/plans/world-build-system/06-phase-ecosystem.md new file mode 100644 index 0000000..e7042d2 --- /dev/null +++ b/plans/world-build-system/06-phase-ecosystem.md @@ -0,0 +1,322 @@ +# Phase 6: Ecosystem Interactions + +## Overview + +Implement the ecosystem where neutrals interact with each other based on proximity, creating hybrid terrain features and chain reactions that add emergent gameplay. + +## Goals + +- [ ] Implement neutral proximity detection system +- [ ] Create hybrid terrain features (Enchanted Grove, Power Conduit, Overgrown Ruins) +- [ ] Add chain reaction effects when harvesting +- [ ] Create hybrid neutral types +- [ ] Implement ecosystem visualization (connection lines, auras) + +## Proximity Detection System + +```javascript +// js/systems/EcosystemSystem.js +export class EcosystemSystem { + constructor(gameState) { + this.gameState = gameState; + this.proximityRadius = 150; + this.hybridFeatures = []; + this.connections = []; + } + + update(deltaTime) { + this.updateConnections(); + this.checkForHybridSpawns(); + this.updateHybridFeatures(deltaTime); + } + + updateConnections() { + this.connections = []; + const neutrals = this.gameState.neutralEntities; + + for (let i = 0; i < neutrals.length; i++) { + for (let j = i + 1; j < neutrals.length; j++) { + const n1 = neutrals[i]; + const n2 = neutrals[j]; + + if (n1.alignmentType === n2.alignmentType) continue; + + const dist = this.getDistance(n1, n2); + if (dist <= this.proximityRadius) { + this.connections.push({ + from: n1, + to: n2, + types: [n1.alignmentType, n2.alignmentType].sort(), + strength: 1 - (dist / this.proximityRadius) + }); + } + } + } + } + + getDistance(a, b) { + const dx = a.x - b.x; + const dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy); + } +} +``` + +## Hybrid Terrain Features + +### Enchanted Grove (Arcane + Primal) +- **Appearance**: Glowing plants, bioluminescent flowers +- **Effect**: Heals player 2 HP/sec, regenerates mana +- **Spawns**: Living Crystal hybrid neutrals + +### Power Conduit (Arcane + Forged) +- **Appearance**: Electrified metal structures, arcing lightning +- **Effect**: Damages enemies 3 DPS, powers nearby constructs +- **Spawns**: Charged Ore hybrid neutrals + +### Overgrown Ruins (Primal + Forged) +- **Appearance**: Vines wrapping metal, organic-mechanical fusion +- **Effect**: Creates regenerating walls, slows enemies 40% +- **Spawns**: Ironwood hybrid neutrals + +## Chain Reaction Effects + +When harvesting a neutral near another type: + +```javascript +// In EcosystemSystem.js +onNeutralHarvested(neutral, position) { + const nearbyNeutrals = this.getNeutralsInRange(position, this.proximityRadius); + + for (const nearby of nearbyNeutrals) { + if (nearby.alignmentType === neutral.alignmentType) continue; + + // Apply chain effect based on combination + this.applyChainEffect(neutral.alignmentType, nearby); + } +} + +applyChainEffect(harvestedType, targetNeutral) { + const combo = [harvestedType, targetNeutral.alignmentType].sort().join('_'); + + switch(combo) { + case 'arcane_primal': + // Crystal energizes Seed - faster growth + targetNeutral.growthMultiplier = 1.5; + targetNeutral.bonusYield = 1; + break; + + case 'arcane_forged': + // Crystal charges Ore - becomes magnetic + targetNeutral.charged = true; + targetNeutral.pullRadius = 100; + break; + + case 'forged_primal': + // Ore reinforces Seed - becomes armored + targetNeutral.armor = 5; + targetNeutral.bonusYield = 2; + break; + } +} +``` + +## Hybrid Neutral Types + +### Living Crystal (Arcane + Primal) +```javascript +// js/entities/neutrals/hybrid/LivingCrystal.js +export class LivingCrystal extends NeutralEntity { + constructor(x, y) { + super(x, y, 40); + this.type = 'living_crystal'; + this.alignmentTypes = ['arcane', 'primal']; + this.color = '#00ffaa'; + + this.harvestReward = { + arcane: 3, + primal: 3 + }; + + // Unique: slowly regenerates fill level + this.regenRate = 0.5; + } + + update(deltaTime) { + super.update(deltaTime); + + // Regenerate if not full + if (this.fillLevel < this.maxFill && this.fillLevel > 0) { + this.fillLevel = Math.min( + this.maxFill, + this.fillLevel + this.regenRate * deltaTime + ); + } + } +} +``` + +### Charged Ore (Arcane + Forged) +```javascript +// js/entities/neutrals/hybrid/ChargedOre.js +export class ChargedOre extends NeutralEntity { + constructor(x, y) { + super(x, y, 45); + this.type = 'charged_ore'; + this.alignmentTypes = ['arcane', 'forged']; + this.color = '#8888ff'; + + this.harvestReward = { + arcane: 3, + forged: 3 + }; + + // Unique: damages nearby enemies + this.damageRadius = 80; + this.damagePerSecond = 1; + } + + update(deltaTime, enemies) { + super.update(deltaTime); + + // Damage nearby enemies + for (const enemy of enemies) { + const dist = this.getDistanceTo(enemy); + if (dist <= this.damageRadius) { + enemy.takeDamage(this.damagePerSecond * deltaTime, 'arcane'); + } + } + } +} +``` + +### Ironwood (Primal + Forged) +```javascript +// js/entities/neutrals/hybrid/Ironwood.js +export class Ironwood extends NeutralEntity { + constructor(x, y) { + super(x, y, 50); + this.type = 'ironwood'; + this.alignmentTypes = ['primal', 'forged']; + this.color = '#668844'; + + this.harvestReward = { + primal: 3, + forged: 3 + }; + + // Unique: extremely heavy, creates large safe zone + this.mass = 500; + this.blockRadius = 100; + } +} +``` + +## Connection Visualization + +```javascript +// js/ui/EcosystemVisuals.js +export class EcosystemVisuals { + constructor(ecosystemSystem) { + this.ecosystemSystem = ecosystemSystem; + this.pulsePhase = 0; + } + + update(deltaTime) { + this.pulsePhase += deltaTime * 2; + } + + draw(ctx, cameraX, cameraY) { + // Draw connections between nearby neutrals + for (const conn of this.ecosystemSystem.connections) { + this.drawConnection(ctx, conn, cameraX, cameraY); + } + } + + drawConnection(ctx, connection, cameraX, cameraY) { + const x1 = connection.from.x - cameraX; + const y1 = connection.from.y - cameraY; + const x2 = connection.to.x - cameraX; + const y2 = connection.to.y - cameraY; + + // Get color based on types + const color = this.getConnectionColor(connection.types); + const alpha = connection.strength * 0.5 * (0.7 + 0.3 * Math.sin(this.pulsePhase)); + + // Draw glowing line + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.strokeStyle = color.replace('1)', `${alpha})`); + ctx.lineWidth = 3; + ctx.stroke(); + + // Draw energy particles along line + this.drawEnergyParticles(ctx, x1, y1, x2, y2, color); + } + + getConnectionColor(types) { + if (types.includes('arcane') && types.includes('primal')) { + return 'rgba(0, 255, 170, 1)'; + } else if (types.includes('arcane') && types.includes('forged')) { + return 'rgba(136, 136, 255, 1)'; + } else { + return 'rgba(102, 136, 68, 1)'; + } + } + + drawEnergyParticles(ctx, x1, y1, x2, y2, color) { + const particleCount = 3; + for (let i = 0; i < particleCount; i++) { + const t = ((this.pulsePhase * 0.5 + i / particleCount) % 1); + const px = x1 + (x2 - x1) * t; + const py = y1 + (y2 - y1) * t; + + ctx.beginPath(); + ctx.arc(px, py, 4, 0, Math.PI * 2); + ctx.fillStyle = color; + ctx.fill(); + } + } +} +``` + +## File Structure After Phase 6 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── neutrals/ +│ │ ├── hybrid/ +│ │ │ ├── LivingCrystal.js # NEW +│ │ │ ├── ChargedOre.js # NEW +│ │ │ └── Ironwood.js # NEW +│ ├── terrain/ +│ │ ├── hybrid/ +│ │ │ ├── EnchantedGrove.js # NEW +│ │ │ ├── PowerConduit.js # NEW +│ │ │ └── OvergrownRuins.js # NEW +├── systems/ +│ └── EcosystemSystem.js # NEW +├── ui/ +│ └── EcosystemVisuals.js # NEW +└── ... +``` + +## Testing Checklist + +- [ ] Connections form between nearby different-type neutrals +- [ ] Connection strength scales with distance +- [ ] Hybrid terrain spawns at connection midpoints +- [ ] Chain reactions trigger on harvest +- [ ] Hybrid neutrals spawn from hybrid terrain +- [ ] Visual connections render correctly +- [ ] Energy particles animate along connections + +## Dependencies + +- Phase 1-5 (all previous phases) + +## Next Phase + +[Phase 7: Dual Map System](./07-phase-dual-map.md) - Implement the Creation Map for peaceful building and planning. diff --git a/plans/world-build-system/07-phase-dual-map.md b/plans/world-build-system/07-phase-dual-map.md new file mode 100644 index 0000000..aadb806 --- /dev/null +++ b/plans/world-build-system/07-phase-dual-map.md @@ -0,0 +1,496 @@ +# Phase 7: Dual Map System + +## Overview + +Implement the dual map system with a PvE Map for combat and a Creation Map for peaceful planning, planting, and aspect management. + +## Goals + +- [ ] Create MapManager for switching between maps +- [ ] Implement Creation Map state (paused enemies, builder UI) +- [ ] Add seed planting system +- [ ] Create Aspect management UI for Creation Map +- [ ] Implement map transition effects +- [ ] Add enemy distribution preview +- [ ] Sync planted seeds to PvE Map as growing neutrals + +## Map Manager + +```javascript +// js/systems/MapManager.js +export class MapManager { + constructor(gameState) { + this.gameState = gameState; + this.currentMap = 'pve'; // 'pve' or 'creation' + this.transitionProgress = 0; + this.isTransitioning = false; + this.transitionDuration = 0.3; + } + + switchMap() { + if (this.isTransitioning) return; + + this.isTransitioning = true; + this.transitionProgress = 0; + + // Target map + this.targetMap = this.currentMap === 'pve' ? 'creation' : 'pve'; + } + + update(deltaTime) { + if (!this.isTransitioning) return; + + this.transitionProgress += deltaTime / this.transitionDuration; + + if (this.transitionProgress >= 1) { + this.completeTransition(); + } + } + + completeTransition() { + this.currentMap = this.targetMap; + this.isTransitioning = false; + this.transitionProgress = 0; + + if (this.currentMap === 'creation') { + this.enterCreationMode(); + } else { + this.enterPvEMode(); + } + } + + enterCreationMode() { + // Pause enemy AI + this.gameState.enemiesPaused = true; + + // Show builder UI + this.gameState.showBuilderUI = true; + + // Enable placement mode + this.gameState.placementMode = true; + } + + enterPvEMode() { + // Resume enemy AI + this.gameState.enemiesPaused = false; + + // Hide builder UI + this.gameState.showBuilderUI = false; + + // Disable placement mode + this.gameState.placementMode = false; + + // Sync any planted seeds + this.syncPlantedSeeds(); + } + + syncPlantedSeeds() { + for (const seed of this.gameState.plantedSeeds) { + if (!seed.synced) { + // Create growing neutral at seed position + const neutral = this.createNeutralFromSeed(seed); + this.gameState.neutralEntities.push(neutral); + seed.synced = true; + } + } + } + + createNeutralFromSeed(seed) { + // Create neutral based on seed type + switch(seed.type) { + case 'arcane': + return new CrystalNode(seed.x, seed.y); + case 'primal': + return new SeedPod(seed.x, seed.y); + case 'forged': + return new OreVein(seed.x, seed.y); + } + } + + isPvEMap() { + return this.currentMap === 'pve'; + } + + isCreationMap() { + return this.currentMap === 'creation'; + } +} +``` + +## Seed Planting System + +```javascript +// js/systems/PlantingSystem.js +export class PlantingSystem { + constructor(gameState) { + this.gameState = gameState; + this.selectedSeedType = null; + this.placementPreview = null; + this.plantedSeeds = []; + } + + selectSeedType(type) { + this.selectedSeedType = type; + } + + updatePreview(mouseX, mouseY) { + if (!this.selectedSeedType) { + this.placementPreview = null; + return; + } + + this.placementPreview = { + x: mouseX, + y: mouseY, + type: this.selectedSeedType, + valid: this.isValidPlacement(mouseX, mouseY) + }; + } + + isValidPlacement(x, y) { + // Check minimum distance from other neutrals/seeds + const minDistance = 100; + + for (const neutral of this.gameState.neutralEntities) { + const dist = Math.sqrt((neutral.x - x) ** 2 + (neutral.y - y) ** 2); + if (dist < minDistance) return false; + } + + for (const seed of this.plantedSeeds) { + const dist = Math.sqrt((seed.x - x) ** 2 + (seed.y - y) ** 2); + if (dist < minDistance) return false; + } + + return true; + } + + plantSeed(x, y) { + if (!this.selectedSeedType) return false; + if (!this.isValidPlacement(x, y)) return false; + + // Check if player has seeds + const seedCount = this.gameState.resources.seeds[this.selectedSeedType] || 0; + if (seedCount <= 0) return false; + + // Consume seed + this.gameState.resources.seeds[this.selectedSeedType]--; + + // Create planted seed + const seed = { + x, y, + type: this.selectedSeedType, + plantedAt: Date.now(), + growthTime: 60, // seconds to mature + synced: false + }; + + this.plantedSeeds.push(seed); + this.gameState.plantedSeeds.push(seed); + + return true; + } + + drawPreview(ctx, cameraX, cameraY) { + if (!this.placementPreview) return; + + const screenX = this.placementPreview.x - cameraX; + const screenY = this.placementPreview.y - cameraY; + + // Draw preview circle + ctx.beginPath(); + ctx.arc(screenX, screenY, 30, 0, Math.PI * 2); + ctx.fillStyle = this.placementPreview.valid ? + 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)'; + ctx.fill(); + ctx.strokeStyle = this.placementPreview.valid ? '#00ff00' : '#ff0000'; + ctx.stroke(); + + // Draw type indicator + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + ctx.fillStyle = colors[this.placementPreview.type]; + ctx.beginPath(); + ctx.arc(screenX, screenY, 15, 0, Math.PI * 2); + ctx.fill(); + } +} +``` + +## Creation Map UI + +```javascript +// js/ui/CreationMapUI.js +export class CreationMapUI { + constructor(gameState, aspectManager, plantingSystem, alignmentSystem) { + this.gameState = gameState; + this.aspectManager = aspectManager; + this.plantingSystem = plantingSystem; + this.alignmentSystem = alignmentSystem; + + this.panels = { + seeds: { x: 10, y: 100, width: 200, height: 150 }, + aspects: { x: 10, y: 270, width: 200, height: 200 }, + alignment: { x: 10, y: 490, width: 200, height: 100 }, + enemies: { x: 10, y: 610, width: 200, height: 100 } + }; + } + + draw(ctx, canvasWidth, canvasHeight) { + // Semi-transparent overlay + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + // Draw panels + this.drawSeedPanel(ctx); + this.drawAspectPanel(ctx); + this.drawAlignmentPanel(ctx); + this.drawEnemyPreviewPanel(ctx); + + // Draw mode indicator + this.drawModeIndicator(ctx, canvasWidth); + } + + drawSeedPanel(ctx) { + const panel = this.panels.seeds; + this.drawPanelBackground(ctx, panel, 'Seeds'); + + const seeds = this.gameState.resources.seeds; + const types = ['arcane', 'primal', 'forged']; + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + + let y = panel.y + 40; + for (const type of types) { + const count = seeds[type] || 0; + const selected = this.plantingSystem.selectedSeedType === type; + + // Seed button + ctx.fillStyle = selected ? colors[type] : 'rgba(50, 50, 50, 0.8)'; + ctx.fillRect(panel.x + 10, y, 30, 30); + ctx.strokeStyle = colors[type]; + ctx.strokeRect(panel.x + 10, y, 30, 30); + + // Label and count + ctx.fillStyle = '#ffffff'; + ctx.font = '14px Arial'; + ctx.fillText(`${type}: ${count}`, panel.x + 50, y + 20); + + y += 40; + } + } + + drawAspectPanel(ctx) { + const panel = this.panels.aspects; + this.drawPanelBackground(ctx, panel, 'Aspects'); + + // Equipped aspects + ctx.fillStyle = '#aaaaaa'; + ctx.font = '12px Arial'; + ctx.fillText('Equipped:', panel.x + 10, panel.y + 40); + + let y = panel.y + 55; + for (const aspect of this.aspectManager.equipped) { + ctx.fillStyle = this.getAspectColor(aspect.alignmentType); + ctx.fillRect(panel.x + 10, y, 20, 20); + ctx.fillStyle = '#ffffff'; + ctx.fillText(aspect.name, panel.x + 35, y + 15); + y += 25; + } + + // Inventory + ctx.fillStyle = '#aaaaaa'; + ctx.fillText('Inventory:', panel.x + 10, y + 15); + y += 30; + + for (const aspect of this.aspectManager.inventory.slice(0, 4)) { + ctx.fillStyle = this.getAspectColor(aspect.alignmentType); + ctx.fillRect(panel.x + 10, y, 20, 20); + ctx.fillStyle = '#ffffff'; + ctx.fillText(aspect.name, panel.x + 35, y + 15); + y += 25; + } + } + + drawAlignmentPanel(ctx) { + const panel = this.panels.alignment; + this.drawPanelBackground(ctx, panel, 'Alignment'); + + const alignment = this.alignmentSystem.getAlignment(); + const barWidth = panel.width - 20; + const barHeight = 20; + let y = panel.y + 40; + + // Stacked bar + ctx.fillStyle = '#00aaff'; + ctx.fillRect(panel.x + 10, y, barWidth * (alignment.arcane / 100), barHeight); + + ctx.fillStyle = '#44aa44'; + ctx.fillRect(panel.x + 10 + barWidth * (alignment.arcane / 100), y, + barWidth * (alignment.primal / 100), barHeight); + + ctx.fillStyle = '#ff8844'; + ctx.fillRect(panel.x + 10 + barWidth * ((alignment.arcane + alignment.primal) / 100), y, + barWidth * (alignment.forged / 100), barHeight); + + // Labels + y += 30; + ctx.fillStyle = '#ffffff'; + ctx.font = '12px Arial'; + ctx.fillText(`Arcane: ${alignment.arcane.toFixed(0)}%`, panel.x + 10, y); + ctx.fillText(`Primal: ${alignment.primal.toFixed(0)}%`, panel.x + 10, y + 15); + ctx.fillText(`Forged: ${alignment.forged.toFixed(0)}%`, panel.x + 10, y + 30); + } + + drawEnemyPreviewPanel(ctx) { + const panel = this.panels.enemies; + this.drawPanelBackground(ctx, panel, 'Enemy Distribution'); + + const alignment = this.alignmentSystem.getAlignment(); + + ctx.fillStyle = '#ffffff'; + ctx.font = '12px Arial'; + + let y = panel.y + 40; + ctx.fillStyle = '#6644ff'; + ctx.fillText(`Arcane Enemies: ${alignment.arcane.toFixed(0)}%`, panel.x + 10, y); + + ctx.fillStyle = '#88aa44'; + ctx.fillText(`Primal Enemies: ${alignment.primal.toFixed(0)}%`, panel.x + 10, y + 20); + + ctx.fillStyle = '#aa6644'; + ctx.fillText(`Forged Enemies: ${alignment.forged.toFixed(0)}%`, panel.x + 10, y + 40); + } + + drawPanelBackground(ctx, panel, title) { + ctx.fillStyle = 'rgba(20, 20, 30, 0.9)'; + ctx.fillRect(panel.x, panel.y, panel.width, panel.height); + ctx.strokeStyle = '#444'; + ctx.strokeRect(panel.x, panel.y, panel.width, panel.height); + + ctx.fillStyle = '#ffffff'; + ctx.font = 'bold 14px Arial'; + ctx.fillText(title, panel.x + 10, panel.y + 20); + } + + drawModeIndicator(ctx, canvasWidth) { + ctx.fillStyle = 'rgba(0, 100, 50, 0.8)'; + ctx.fillRect(canvasWidth / 2 - 100, 10, 200, 40); + + ctx.fillStyle = '#ffffff'; + ctx.font = 'bold 18px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('CREATION MODE', canvasWidth / 2, 35); + ctx.textAlign = 'left'; + + ctx.font = '12px Arial'; + ctx.fillText('Press TAB to return to combat', canvasWidth / 2 - 80, 55); + } + + getAspectColor(type) { + const colors = { + arcane: '#00aaff', + primal: '#44aa44', + forged: '#ff8844' + }; + return colors[type] || '#888888'; + } +} +``` + +## Map Transition Effect + +```javascript +// js/ui/MapTransition.js +export class MapTransition { + constructor() { + this.progress = 0; + this.fromMap = null; + this.toMap = null; + } + + start(from, to) { + this.fromMap = from; + this.toMap = to; + this.progress = 0; + } + + update(deltaTime) { + this.progress = Math.min(1, this.progress + deltaTime * 3); + } + + draw(ctx, canvasWidth, canvasHeight, playerX, playerY, cameraX, cameraY) { + if (this.progress <= 0) return; + + const screenX = playerX - cameraX; + const screenY = playerY - cameraY; + + // Ripple effect from player position + const maxRadius = Math.max(canvasWidth, canvasHeight) * 1.5; + const currentRadius = maxRadius * this.progress; + + ctx.save(); + + // Create circular reveal + ctx.beginPath(); + ctx.arc(screenX, screenY, currentRadius, 0, Math.PI * 2); + + // Color based on target map + if (this.toMap === 'creation') { + ctx.fillStyle = `rgba(0, 50, 30, ${0.5 * (1 - this.progress)})`; + } else { + ctx.fillStyle = `rgba(50, 0, 0, ${0.5 * (1 - this.progress)})`; + } + + ctx.fill(); + + // Edge glow + ctx.strokeStyle = this.toMap === 'creation' ? '#00ff88' : '#ff4444'; + ctx.lineWidth = 5; + ctx.stroke(); + + ctx.restore(); + } + + isComplete() { + return this.progress >= 1; + } +} +``` + +## File Structure After Phase 7 + +``` +vampire-survivor/js/ +├── systems/ +│ ├── MapManager.js # NEW +│ └── PlantingSystem.js # NEW +├── ui/ +│ ├── CreationMapUI.js # NEW +│ └── MapTransition.js # NEW +└── ... +``` + +## Testing Checklist + +- [ ] Map switching works with TAB key +- [ ] Transition effect plays smoothly +- [ ] Enemies pause in Creation Map +- [ ] Seeds can be selected and planted +- [ ] Invalid placements show red preview +- [ ] Planted seeds sync to PvE Map +- [ ] Aspect management works in Creation Map +- [ ] Alignment and enemy preview display correctly + +## Dependencies + +- Phase 1-6 (all previous phases) + +## Next Phase + +[Phase 8: Polish and Balance](./08-phase-polish.md) - Final polish, hybrid aspects, and balance tuning. diff --git a/plans/world-build-system/08-phase-polish.md b/plans/world-build-system/08-phase-polish.md new file mode 100644 index 0000000..f451408 --- /dev/null +++ b/plans/world-build-system/08-phase-polish.md @@ -0,0 +1,530 @@ +# Phase 8: Polish and Balance + +## Overview + +Final phase focusing on hybrid aspects, visual polish, audio feedback, balance tuning, and overall game feel improvements. + +## Goals + +- [ ] Implement hybrid Aspects (require mixed alignment) +- [ ] Add visual effects polish (particles, screen effects) +- [ ] Implement audio feedback system +- [ ] Balance pass on all systems +- [ ] Add tutorial hints +- [ ] Performance optimization +- [ ] Bug fixes and edge cases + +## Hybrid Aspects + +### Living Steel (40% Primal + 40% Forged) +- **Effect**: Armor regenerates 2/sec up to +20 bonus armor +- **Requirement**: Both Primal and Forged at 40%+ + +### Storm Caller (40% Arcane + 40% Primal) +- **Effect**: Lightning spreads through vegetation, +100% damage in vegetation +- **Requirement**: Both Arcane and Primal at 40%+ + +### Arcane Forge (40% Arcane + 40% Forged) +- **Effect**: Constructs gain energy shields (50% of health) +- **Requirement**: Both Arcane and Forged at 40%+ + +### Elemental Mastery (30% each) +- **Effect**: +15% bonus to all damage types +- **Requirement**: All three alignments at 30%+ + +### World Shaper (50% any + 25% each other) +- **Effect**: Terrain features are 50% stronger +- **Requirement**: One dominant alignment with others balanced + +## Hybrid Aspect System + +```javascript +// js/systems/HybridAspectSystem.js +export class HybridAspectSystem { + constructor(alignmentSystem) { + this.alignmentSystem = alignmentSystem; + } + + canUseHybridAspect(aspect) { + const alignment = this.alignmentSystem.getAlignment(); + + switch(aspect.id) { + case 'living_steel': + return alignment.primal >= 40 && alignment.forged >= 40; + case 'storm_caller': + return alignment.arcane >= 40 && alignment.primal >= 40; + case 'arcane_forge': + return alignment.arcane >= 40 && alignment.forged >= 40; + case 'elemental_mastery': + return alignment.arcane >= 30 && alignment.primal >= 30 && alignment.forged >= 30; + case 'world_shaper': + return this.checkWorldShaper(alignment); + default: + return true; + } + } + + checkWorldShaper(alignment) { + const values = [alignment.arcane, alignment.primal, alignment.forged]; + const max = Math.max(...values); + const others = values.filter(v => v !== max); + return max >= 50 && others.every(v => v >= 25); + } + + getHybridBonus(aspect, alignment) { + if (!this.canUseHybridAspect(aspect)) { + return 0; // Aspect disabled + } + + // Hybrid aspects get bonus based on how well requirements are met + // Exceeding requirements gives up to 50% bonus + return this.calculateExcessBonus(aspect, alignment); + } +} +``` + +## Visual Effects Polish + +### Particle System + +```javascript +// js/effects/ParticleSystem.js +export class ParticleSystem { + constructor() { + this.particles = []; + this.emitters = []; + } + + createBurst(x, y, color, count = 10) { + for (let i = 0; i < count; i++) { + const angle = (i / count) * Math.PI * 2; + const speed = 50 + Math.random() * 100; + + this.particles.push({ + x, y, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + color, + size: 3 + Math.random() * 3, + life: 0.5 + Math.random() * 0.5, + maxLife: 1 + }); + } + } + + createTrail(x, y, color) { + this.particles.push({ + x: x + (Math.random() - 0.5) * 10, + y: y + (Math.random() - 0.5) * 10, + vx: (Math.random() - 0.5) * 20, + vy: -20 - Math.random() * 30, + color, + size: 2 + Math.random() * 2, + life: 0.3 + Math.random() * 0.3, + maxLife: 0.6 + }); + } + + update(deltaTime) { + for (let i = this.particles.length - 1; i >= 0; i--) { + const p = this.particles[i]; + + p.x += p.vx * deltaTime; + p.y += p.vy * deltaTime; + p.life -= deltaTime; + + // Gravity + p.vy += 100 * deltaTime; + + // Friction + p.vx *= 0.98; + p.vy *= 0.98; + + if (p.life <= 0) { + this.particles.splice(i, 1); + } + } + } + + draw(ctx, cameraX, cameraY) { + for (const p of this.particles) { + const screenX = p.x - cameraX; + const screenY = p.y - cameraY; + const alpha = p.life / p.maxLife; + + ctx.beginPath(); + ctx.arc(screenX, screenY, p.size * alpha, 0, Math.PI * 2); + ctx.fillStyle = p.color.replace('1)', `${alpha})`); + ctx.fill(); + } + } +} +``` + +### Screen Effects + +```javascript +// js/effects/ScreenEffects.js +export class ScreenEffects { + constructor() { + this.shakeAmount = 0; + this.shakeDecay = 5; + this.flashColor = null; + this.flashAlpha = 0; + } + + shake(amount) { + this.shakeAmount = Math.max(this.shakeAmount, amount); + } + + flash(color, alpha = 0.3) { + this.flashColor = color; + this.flashAlpha = alpha; + } + + update(deltaTime) { + // Decay shake + this.shakeAmount = Math.max(0, this.shakeAmount - this.shakeDecay * deltaTime); + + // Decay flash + this.flashAlpha = Math.max(0, this.flashAlpha - deltaTime * 2); + } + + getShakeOffset() { + if (this.shakeAmount <= 0) return { x: 0, y: 0 }; + + return { + x: (Math.random() - 0.5) * this.shakeAmount * 2, + y: (Math.random() - 0.5) * this.shakeAmount * 2 + }; + } + + drawFlash(ctx, canvasWidth, canvasHeight) { + if (this.flashAlpha <= 0) return; + + ctx.fillStyle = this.flashColor.replace('1)', `${this.flashAlpha})`); + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + } +} +``` + +## Audio Feedback System + +```javascript +// js/audio/AudioManager.js +export class AudioManager { + constructor() { + this.sounds = {}; + this.musicVolume = 0.5; + this.sfxVolume = 0.7; + this.enabled = true; + } + + // Sound definitions (would load actual audio files) + soundDefinitions = { + harvest: { frequency: 440, duration: 0.1, type: 'sine' }, + hit: { frequency: 200, duration: 0.05, type: 'square' }, + levelUp: { frequency: 880, duration: 0.3, type: 'sine' }, + aspectDrop: { frequency: 660, duration: 0.2, type: 'triangle' }, + mapSwitch: { frequency: 330, duration: 0.15, type: 'sine' }, + pureBuild: { frequency: 1000, duration: 0.5, type: 'sine' } + }; + + play(soundName) { + if (!this.enabled) return; + + const def = this.soundDefinitions[soundName]; + if (!def) return; + + // Web Audio API implementation + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + const oscillator = audioCtx.createOscillator(); + const gainNode = audioCtx.createGain(); + + oscillator.type = def.type; + oscillator.frequency.setValueAtTime(def.frequency, audioCtx.currentTime); + + gainNode.gain.setValueAtTime(this.sfxVolume, audioCtx.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + def.duration); + + oscillator.connect(gainNode); + gainNode.connect(audioCtx.destination); + + oscillator.start(); + oscillator.stop(audioCtx.currentTime + def.duration); + } + + playAlignmentSound(alignmentType) { + const frequencies = { + arcane: 660, + primal: 440, + forged: 330 + }; + + // Custom sound based on alignment + // ... + } +} +``` + +## Balance Configuration + +```javascript +// js/config/BalanceConfig.js +export const BalanceConfig = { + // Neutral harvesting + neutrals: { + fillRequired: 10, + harvestAlignmentGain: 5, + aspectDropRate: 0.20, + aspectDropRatePerFill: 0.01 + }, + + // Alignment scaling + alignment: { + thresholds: { + minor: 40, + major: 60, + pure: 80 + }, + bonuses: { + minor: 1.25, + major: 1.5, + pure: 2.0 + }, + penalties: { + minor: 0.9, + major: 0.75, + pure: 0.5 + } + }, + + // Enemy spawning + enemies: { + baseSpawnRate: 2.0, // seconds + maxEnemies: 50, + spawnDistance: 400, + resistanceMultiplier: 0.5, + weaknessMultiplier: 1.5 + }, + + // Terrain features + terrain: { + energyPoolDamage: 2, + vegetationHeal: 1, + vegetationSlow: 0.2, + metalPlateSlow: 0.3, + turretDamage: 1, + turretFireRate: 1.0 + }, + + // Aspects + aspects: { + maxEquipped: 3, + maxInventory: 10, + hybridRequirement: 40 + }, + + // Dual map + maps: { + transitionDuration: 0.3, + seedGrowthTime: 60, + minSeedDistance: 100 + } +}; +``` + +## Tutorial Hints System + +```javascript +// js/ui/TutorialHints.js +export class TutorialHints { + constructor() { + this.hints = [ + { id: 'first_neutral', text: 'Shoot neutrals to fill them, then walk over to harvest', shown: false }, + { id: 'alignment', text: 'Harvesting shifts world alignment - this affects enemies and abilities', shown: false }, + { id: 'aspect_drop', text: 'Aspects drop from harvested neutrals - equip them for power!', shown: false }, + { id: 'creation_map', text: 'Press TAB to enter Creation Mode and plan your strategy', shown: false }, + { id: 'pure_build', text: 'Reach 80% alignment for Pure Build bonuses!', shown: false }, + { id: 'hybrid_terrain', text: 'Place different neutral types near each other for hybrid effects', shown: false } + ]; + + this.currentHint = null; + this.hintTimer = 0; + this.hintDuration = 5; + } + + triggerHint(hintId) { + const hint = this.hints.find(h => h.id === hintId); + if (!hint || hint.shown) return; + + hint.shown = true; + this.currentHint = hint; + this.hintTimer = this.hintDuration; + } + + update(deltaTime) { + if (this.hintTimer > 0) { + this.hintTimer -= deltaTime; + if (this.hintTimer <= 0) { + this.currentHint = null; + } + } + } + + draw(ctx, canvasWidth, canvasHeight) { + if (!this.currentHint) return; + + const alpha = Math.min(1, this.hintTimer); + + // Hint box at top of screen + ctx.fillStyle = `rgba(0, 0, 0, ${0.8 * alpha})`; + ctx.fillRect(canvasWidth / 2 - 200, 60, 400, 40); + + ctx.fillStyle = `rgba(255, 255, 100, ${alpha})`; + ctx.font = '14px Arial'; + ctx.textAlign = 'center'; + ctx.fillText(this.currentHint.text, canvasWidth / 2, 85); + ctx.textAlign = 'left'; + } +} +``` + +## Performance Optimization + +```javascript +// js/systems/PerformanceOptimizer.js +export class PerformanceOptimizer { + constructor() { + this.spatialHash = new Map(); + this.cellSize = 100; + this.frameTimeHistory = []; + this.targetFPS = 60; + } + + // Spatial hashing for collision detection + updateSpatialHash(entities) { + this.spatialHash.clear(); + + for (const entity of entities) { + const cellX = Math.floor(entity.x / this.cellSize); + const cellY = Math.floor(entity.y / this.cellSize); + const key = `${cellX},${cellY}`; + + if (!this.spatialHash.has(key)) { + this.spatialHash.set(key, []); + } + this.spatialHash.get(key).push(entity); + } + } + + getNearbyEntities(x, y, radius) { + const nearby = []; + const cellRadius = Math.ceil(radius / this.cellSize); + const centerCellX = Math.floor(x / this.cellSize); + const centerCellY = Math.floor(y / this.cellSize); + + for (let dx = -cellRadius; dx <= cellRadius; dx++) { + for (let dy = -cellRadius; dy <= cellRadius; dy++) { + const key = `${centerCellX + dx},${centerCellY + dy}`; + const cell = this.spatialHash.get(key); + if (cell) { + nearby.push(...cell); + } + } + } + + return nearby; + } + + // Adaptive quality based on performance + trackFrameTime(deltaTime) { + this.frameTimeHistory.push(deltaTime); + if (this.frameTimeHistory.length > 60) { + this.frameTimeHistory.shift(); + } + } + + getQualityLevel() { + const avgFrameTime = this.frameTimeHistory.reduce((a, b) => a + b, 0) / this.frameTimeHistory.length; + const currentFPS = 1 / avgFrameTime; + + if (currentFPS < 30) return 'low'; + if (currentFPS < 45) return 'medium'; + return 'high'; + } +} +``` + +## Final Testing Checklist + +### Gameplay +- [ ] All neutral types work correctly +- [ ] All enemy types spawn and behave correctly +- [ ] Alignment system tracks and scales properly +- [ ] All terrain features function as designed +- [ ] All aspects work and scale with alignment +- [ ] Hybrid aspects activate at correct thresholds +- [ ] Dual map switching works smoothly +- [ ] Seed planting and growth works + +### Balance +- [ ] Pure builds feel powerful but challenging +- [ ] Hybrid builds are viable alternatives +- [ ] Enemy resistance creates interesting gameplay +- [ ] Terrain features provide meaningful advantage +- [ ] Aspect drops feel rewarding +- [ ] Difficulty scales appropriately over time + +### Polish +- [ ] Particle effects enhance feedback +- [ ] Screen shake feels impactful +- [ ] Audio cues are clear and satisfying +- [ ] Tutorial hints appear at right times +- [ ] UI is clear and readable +- [ ] Transitions are smooth + +### Performance +- [ ] Maintains 60 FPS with 50 enemies +- [ ] No memory leaks over extended play +- [ ] Spatial hashing improves collision checks +- [ ] Quality adapts to device capability + +## File Structure After Phase 8 + +``` +vampire-survivor/js/ +├── entities/ +│ ├── aspects/ +│ │ └── hybrid/ +│ │ ├── LivingSteel.js # NEW +│ │ ├── StormCaller.js # NEW +│ │ ├── ArcaneForge.js # NEW +│ │ ├── ElementalMastery.js # NEW +│ │ └── WorldShaper.js # NEW +├── effects/ +│ ├── ParticleSystem.js # NEW +│ └── ScreenEffects.js # NEW +├── audio/ +│ └── AudioManager.js # NEW +├── systems/ +│ ├── HybridAspectSystem.js # NEW +│ └── PerformanceOptimizer.js # NEW +├── ui/ +│ └── TutorialHints.js # NEW +├── config/ +│ └── BalanceConfig.js # NEW +└── ... +``` + +## Conclusion + +This completes the World Build System implementation plan. The system transforms neutral mobs from simple obstacles into a deep strategic layer that: + +1. **Terraforms the world** through harvesting choices +2. **Shifts alignment** affecting abilities and enemies +3. **Creates ecosystem interactions** between neutral types +4. **Rewards build commitment** with powerful synergies +5. **Provides dual-map gameplay** for planning and combat + +The 8-phase implementation allows incremental development while maintaining a playable game at each stage. From a04f1cc690f373231dc48db54a7fd95343f5ed36 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 8 Mar 2026 11:47:24 +0000 Subject: [PATCH 2/2] docs: retheme World Build System to Garden Defense - Replace Arcane/Primal/Forged with Flowers/Vegetables/Trees - Replace abstract enemies with realistic garden pests (Aphids, Rabbits, Beetles, etc.) - Rename Aspects to Gardener Abilities with garden-themed powers - Update terrain features to garden elements (Pollen Clouds, Scarecrows, Bird Houses) - Rename Ecosystem to Companion Planting system - Update Dual Map to Garden Plot / Planning Mode - Player is now a Gardener defending their plot from pests --- plans/world-build-system/00-overview.md | 114 ++-- .../01-phase-neutral-types.md | 387 ++++++------ .../02-phase-enemy-types.md | 232 +++---- .../03-phase-alignment-effects.md | 467 ++++++-------- .../04-phase-aspect-foundation.md | 356 +++-------- .../05-phase-aspect-scaling.md | 382 ++---------- .../world-build-system/06-phase-ecosystem.md | 343 +++++------ plans/world-build-system/07-phase-dual-map.md | 427 ++++--------- plans/world-build-system/08-phase-polish.md | 568 ++++-------------- 9 files changed, 1004 insertions(+), 2272 deletions(-) diff --git a/plans/world-build-system/00-overview.md b/plans/world-build-system/00-overview.md index eed588e..ee82070 100644 --- a/plans/world-build-system/00-overview.md +++ b/plans/world-build-system/00-overview.md @@ -1,90 +1,90 @@ -# World Build System - Overview +# Garden Defense System - Overview ## Executive Summary -This system transforms neutral mobs from simple "armored treasure chests" into a deep strategic layer that defines each run. Inspired by Vampire Survivor's weapon evolutions and Balatro's deck-building synergies. +A vampire-survivor style game where you play as a **Gardener** defending your plot from garden pests. Cultivate three types of plants (Flowers, Vegetables, Trees) to shape your garden's ecosystem, which determines what pests attack and what abilities become powerful. ## Core Concept -Players terraform the world through harvesting choices, shifting the world's **alignment** between three paths (Arcane, Primal, Forged), which determines: -- Which **Aspects** (special abilities) become powerful -- Which **enemy types** are attracted to the world -- What **terrain features** appear +Players tend their garden through cultivation choices, shifting the garden's **focus** between three plant types. This determines: +- Which **Gardener Abilities** become powerful +- Which **pest types** are attracted to your garden +- What **terrain features** develop naturally -## The Three Alignments +## The Three Garden Types -| Alignment | Neutral Type | Color | Theme | Playstyle | -|-----------|--------------|-------|-------|-----------| -| **Arcane** | Crystal Node | Blue/Cyan | Magic, Energy | Burst damage, mobility | -| **Primal** | Seed Pod | Green | Nature, Growth | Sustain, area control | -| **Forged** | Ore Vein | Gray/Orange | Industry, Metal | Defense, constructs | +| Garden Focus | Plant Type | Color | Theme | Playstyle | +|--------------|------------|-------|-------|-----------| +| **Flowers** | Flower Buds | Pink/Purple | Beauty, pollination | Burst damage, mobility | +| **Vegetables** | Veggie Sprouts | Green/Orange | Sustenance, growth | Sustain, area control | +| **Trees** | Saplings | Brown/Green | Structure, shelter | Defense, constructs | ## Key Systems -### 1. Alignment System -- World alignment tracked as percentages (always sum to 100%) -- Harvesting shifts alignment toward that neutral's type +### 1. Garden Focus System +- Garden focus tracked as percentages (always sum to 100%) +- Harvesting plants shifts focus toward that type - Thresholds at 40%, 60%, 80% trigger effects -### 2. Enemy Attraction -- Neutrals conceptually attract specific enemy types -- Your cultivation determines your opposition -- Enemies resist damage matching their attraction type +### 2. Pest Attraction +- Different plants attract different pests +- Your garden composition determines your opposition +- Pests resist damage matching their attraction type - Creates natural challenge scaling -### 3. Terrain Terraforming -- Alignment affects what terrain features spawn -- Neutral proximity creates hybrid terrain +### 3. Garden Terrain +- Focus affects what terrain features develop +- Plant proximity creates companion planting effects - Terrain provides strategic advantages -### 4. Aspect System -- Powerful abilities dropped from neutrals -- Scale with matching alignment percentage -- Pure builds: high risk, high reward -- Hybrid builds: safer, more versatile +### 4. Gardener Abilities +- Powerful abilities dropped from harvested plants +- Scale with matching garden focus percentage +- Pure gardens: high risk, high reward +- Mixed gardens: safer, more versatile -### 5. Dual Map System -- PvE Map: Combat, harvest, experience world -- Creation Map: Plan, plant seeds, manage Aspects +### 5. Dual Plot System +- Garden Plot: Combat, harvest, defend +- Planning Mode: Plant seeds, manage abilities ## Build Archetypes -### Pure Builds (80%+ single alignment) -- **The Archmage**: Devastating magic, high mobility -- **The Druid**: Incredible sustain, area control -- **The Engineer**: Strong defense, automated turrets +### Pure Gardens (80%+ single focus) +- **The Florist**: Flower focus, butterflies, fragrance attacks +- **The Farmer**: Vegetable focus, harvest bounty, sustain +- **The Arborist**: Tree focus, sturdy defense, bird allies -### Hybrid Builds (40%+ two alignments) -- **Battle Mage**: Arcane + Forged -- **Shaman**: Arcane + Primal -- **Warden**: Primal + Forged +### Mixed Gardens (40%+ two types) +- **Pollinator Garden**: Flowers + Vegetables +- **Orchard**: Flowers + Trees +- **Food Forest**: Vegetables + Trees ## Implementation Phases -1. **Phase 1**: Multiple Neutral Types -2. **Phase 2**: Enemy Type System -3. **Phase 3**: Alignment Effects -4. **Phase 4**: Aspect System Foundation -5. **Phase 5**: Aspect Scaling -6. **Phase 6**: Ecosystem Interactions -7. **Phase 7**: Dual Map System -8. **Phase 8**: Hybrid Aspects and Polish +1. **Phase 1**: Plant Types (Flower Bud, Veggie Sprout, Sapling) +2. **Phase 2**: Pest System (Aphids, Rabbits, Beetles, etc.) +3. **Phase 3**: Garden Terrain Features +4. **Phase 4**: Gardener Abilities Foundation +5. **Phase 5**: Ability Scaling +6. **Phase 6**: Companion Planting (Ecosystem) +7. **Phase 7**: Dual Plot System +8. **Phase 8**: Polish and Balance ## Success Metrics -- Players feel harvesting choices matter -- Different builds feel meaningfully different -- Enemy resistance creates interesting challenge +- Players feel their gardening choices matter +- Different garden builds feel meaningfully different +- Pest resistance creates interesting challenge - Terrain features create strategic opportunities -- High replayability due to build diversity +- High replayability due to garden diversity ## Related Documents -- [Phase 1: Multiple Neutral Types](./01-phase-neutral-types.md) -- [Phase 2: Enemy Type System](./02-phase-enemy-types.md) -- [Phase 3: Alignment Effects](./03-phase-alignment-effects.md) -- [Phase 4: Aspect System Foundation](./04-phase-aspect-foundation.md) -- [Phase 5: Aspect Scaling](./05-phase-aspect-scaling.md) -- [Phase 6: Ecosystem Interactions](./06-phase-ecosystem.md) -- [Phase 7: Dual Map System](./07-phase-dual-map.md) +- [Phase 1: Plant Types](./01-phase-neutral-types.md) +- [Phase 2: Pest System](./02-phase-enemy-types.md) +- [Phase 3: Garden Terrain](./03-phase-alignment-effects.md) +- [Phase 4: Gardener Abilities](./04-phase-aspect-foundation.md) +- [Phase 5: Ability Scaling](./05-phase-aspect-scaling.md) +- [Phase 6: Companion Planting](./06-phase-ecosystem.md) +- [Phase 7: Dual Plot System](./07-phase-dual-map.md) - [Phase 8: Polish and Balance](./08-phase-polish.md) diff --git a/plans/world-build-system/01-phase-neutral-types.md b/plans/world-build-system/01-phase-neutral-types.md index a15fac4..108ef36 100644 --- a/plans/world-build-system/01-phase-neutral-types.md +++ b/plans/world-build-system/01-phase-neutral-types.md @@ -1,204 +1,198 @@ -# Phase 1: Multiple Neutral Types +# Phase 1: Plant Types ## Overview -Create three distinct neutral entity types that form the foundation of the alignment system. Each type has unique visuals, behaviors, and harvest rewards. +Create three distinct plant types that form the foundation of the garden focus system. Each type has unique visuals, behaviors, and harvest rewards. ## Goals -- [ ] Refactor existing NeutralEntity into base class -- [ ] Create CrystalNode class (Arcane alignment) -- [ ] Create SeedPod class (Primal alignment) -- [ ] Create OreVein class (Forged alignment) -- [ ] Implement basic alignment tracking -- [ ] Add alignment UI display -- [ ] Update spawner to spawn different types +- [ ] Refactor existing NeutralEntity into Plant base class +- [ ] Create FlowerBud class (Flower focus) +- [ ] Create VeggieSprout class (Vegetable focus) +- [ ] Create Sapling class (Tree focus) +- [ ] Implement basic garden focus tracking +- [ ] Add focus UI display +- [ ] Update spawner to spawn different plant types -## The Three Neutral Types +## The Three Plant Types -### 1. Crystal Node (Arcane) +### 1. Flower Bud (Flower Focus) **Visual Design:** -- Translucent blue/cyan crystalline structure -- Internal glow that pulses (sine wave animation) -- Floating particles orbit around it -- When filled: bright beacon effect, crackling energy particles +- Pink/purple bud shape +- Petals slowly unfurl as fill level increases +- Sparkle particles when near full +- When filled: full bloom with butterflies circling **Behavior:** -- Stationary (mass: 100, immovable) -- Creates small light radius (visual only for now) -- Fill level shown as internal glow intensity +- Stationary +- Gentle swaying animation +- Attracts decorative butterflies when full (visual only) **Code Structure:** ```javascript -// js/entities/neutrals/CrystalNode.js -import { NeutralEntity } from './NeutralEntity.js'; +// js/entities/plants/FlowerBud.js +import { Plant } from './Plant.js'; -export class CrystalNode extends NeutralEntity { +export class FlowerBud extends Plant { constructor(x, y) { super(x, y, 35); - this.type = 'crystal'; - this.alignmentType = 'arcane'; - this.color = '#00aaff'; - this.glowColor = '#00ddff'; - this.harvestReward = { type: 'arcane', amount: 5 }; + this.type = 'flower'; + this.focusType = 'flowers'; + this.color = '#ff88cc'; + this.petalColor = '#ffaadd'; + this.harvestReward = { type: 'flowers', amount: 5 }; // Visual properties - this.glowIntensity = 0; - this.particleAngle = 0; - this.pulsePhase = 0; + this.petalOpenness = 0; // 0-1 based on fill + this.swayPhase = Math.random() * Math.PI * 2; + this.sparkleTimer = 0; } update(deltaTime) { super.update(deltaTime); - // Pulsing glow based on fill level - this.pulsePhase += deltaTime * 2; - this.glowIntensity = (this.fillLevel / this.maxFill) * - (0.5 + 0.5 * Math.sin(this.pulsePhase)); + // Petals open based on fill level + this.petalOpenness = this.fillLevel / this.maxFill; - // Orbiting particles - this.particleAngle += deltaTime * 1.5; + // Gentle swaying + this.swayPhase += deltaTime * 1.5; + this.swayOffset = Math.sin(this.swayPhase) * 3; + + // Sparkles when nearly full + if (this.fillLevel > this.maxFill * 0.7) { + this.sparkleTimer += deltaTime; + } } draw(ctx, cameraX, cameraY) { - // Draw glow effect - // Draw crystal shape (hexagonal or diamond) - // Draw orbiting particles - // Draw fill indicator + // Draw stem + // Draw bud/bloom based on petalOpenness + // Draw sparkles if applicable + // Draw butterflies if full } } ``` -### 2. Seed Pod (Primal) +### 2. Veggie Sprout (Vegetable Focus) **Visual Design:** -- Organic green bulb with leaf protrusions -- Breathing/pulsing animation (scale oscillation) -- Small roots visible at base -- When filled: opens slightly, revealing glowing seeds +- Green sprout with broad leaves +- Grows taller as fill level increases +- Orange/red vegetable visible when near full +- When filled: ripe vegetable ready to harvest **Behavior:** -- Slowly drifts in random direction (speed: 10 pixels/sec) -- Leaves trail of small sprout particles -- Changes drift direction periodically +- Stationary but "grows" visually +- Leaves rustle animation +- Small roots visible at base **Code Structure:** ```javascript -// js/entities/neutrals/SeedPod.js -import { NeutralEntity } from './NeutralEntity.js'; +// js/entities/plants/VeggieSprout.js +import { Plant } from './Plant.js'; -export class SeedPod extends NeutralEntity { +export class VeggieSprout extends Plant { constructor(x, y) { super(x, y, 30); - this.type = 'seed'; - this.alignmentType = 'primal'; + this.type = 'vegetable'; + this.focusType = 'vegetables'; this.color = '#44aa44'; - this.glowColor = '#66ff66'; - this.harvestReward = { type: 'primal', amount: 5 }; - - // Movement properties - this.driftSpeed = 10; - this.driftAngle = Math.random() * Math.PI * 2; - this.driftChangeTimer = 0; - this.driftChangeInterval = 3; // seconds + this.vegetableColor = '#ff6622'; + this.harvestReward = { type: 'vegetables', amount: 5 }; // Visual properties - this.breathePhase = 0; - this.breatheScale = 1; + this.growthHeight = 0.3; // 0.3-1.0 based on fill + this.rustlePhase = 0; + this.vegetableSize = 0; } update(deltaTime) { super.update(deltaTime); - // Breathing animation - this.breathePhase += deltaTime * 1.5; - this.breatheScale = 1 + 0.1 * Math.sin(this.breathePhase); + // Grow taller based on fill + this.growthHeight = 0.3 + (this.fillLevel / this.maxFill) * 0.7; - // Drift movement - this.driftChangeTimer += deltaTime; - if (this.driftChangeTimer >= this.driftChangeInterval) { - this.driftAngle += (Math.random() - 0.5) * Math.PI; - this.driftChangeTimer = 0; + // Vegetable appears at 50%+ fill + if (this.fillLevel > this.maxFill * 0.5) { + this.vegetableSize = (this.fillLevel - this.maxFill * 0.5) / (this.maxFill * 0.5); } - this.x += Math.cos(this.driftAngle) * this.driftSpeed * deltaTime; - this.y += Math.sin(this.driftAngle) * this.driftSpeed * deltaTime; + // Leaf rustle + this.rustlePhase += deltaTime * 2; } draw(ctx, cameraX, cameraY) { - // Draw with breathe scale - // Draw bulb shape - // Draw leaf protrusions // Draw roots - // Draw fill indicator (seeds visible inside) + // Draw stem (height based on growthHeight) + // Draw leaves with rustle animation + // Draw vegetable if vegetableSize > 0 } } ``` -### 3. Ore Vein (Forged) +### 3. Sapling (Tree Focus) **Visual Design:** -- Angular metallic rock formation -- Rust-orange and steel-gray coloring -- Metallic sheen (specular highlight) -- When filled: cracks appear revealing glowing ore inside +- Brown trunk with green canopy +- Trunk thickens as fill level increases +- Canopy grows fuller +- When filled: small but sturdy tree **Behavior:** - Completely stationary -- Very heavy (mass: 200) - enemies bounce off strongly -- No animation except crack progression +- Very sturdy (high mass) +- Provides "shade" visual effect **Code Structure:** ```javascript -// js/entities/neutrals/OreVein.js -import { NeutralEntity } from './NeutralEntity.js'; +// js/entities/plants/Sapling.js +import { Plant } from './Plant.js'; -export class OreVein extends NeutralEntity { +export class Sapling extends Plant { constructor(x, y) { super(x, y, 45); - this.type = 'ore'; - this.alignmentType = 'forged'; - this.color = '#888899'; - this.glowColor = '#ffaa44'; - this.harvestReward = { type: 'forged', amount: 5 }; - this.mass = 200; // Extra heavy + this.type = 'tree'; + this.focusType = 'trees'; + this.color = '#8B4513'; // Brown trunk + this.canopyColor = '#228B22'; + this.harvestReward = { type: 'trees', amount: 5 }; + this.mass = 200; // Very sturdy // Visual properties - this.crackLevel = 0; // 0-1 based on fill - this.shimmerPhase = 0; + this.trunkWidth = 0.5; // 0.5-1.0 + this.canopySize = 0.3; // 0.3-1.0 } update(deltaTime) { super.update(deltaTime); - // Update crack level based on fill - this.crackLevel = this.fillLevel / this.maxFill; + // Trunk thickens + this.trunkWidth = 0.5 + (this.fillLevel / this.maxFill) * 0.5; - // Subtle shimmer on metallic surface - this.shimmerPhase += deltaTime * 0.5; + // Canopy grows + this.canopySize = 0.3 + (this.fillLevel / this.maxFill) * 0.7; } draw(ctx, cameraX, cameraY) { - // Draw rock formation (angular polygon) - // Draw metallic sheen - // Draw cracks based on crackLevel - // Draw glowing ore in cracks when filled + // Draw shadow/shade circle + // Draw trunk (width based on trunkWidth) + // Draw canopy (size based on canopySize) } } ``` -## Base Class Refactor +## Base Plant Class ```javascript -// js/entities/neutrals/NeutralEntity.js +// js/entities/plants/Plant.js import { BaseEntity } from '../BaseEntity.js'; -export class NeutralEntity extends BaseEntity { +export class Plant extends BaseEntity { constructor(x, y, size) { super(x, y, size); - this.type = 'neutral'; // Override in subclass - this.alignmentType = null; // 'arcane', 'primal', 'forged' + this.type = 'plant'; + this.focusType = null; // 'flowers', 'vegetables', 'trees' this.fillLevel = 0; this.maxFill = 10; this.targetable = true; @@ -210,10 +204,11 @@ export class NeutralEntity extends BaseEntity { this.mass = 100; } - takeDamage(damage) { + water(amount) { + // "Watering" fills the plant (replaces takeDamage) if (!this.targetable) return false; - this.fillLevel += damage; + this.fillLevel += amount; if (this.fillLevel >= this.maxFill) { this.fillLevel = this.maxFill; this.targetable = false; @@ -223,112 +218,72 @@ export class NeutralEntity extends BaseEntity { } harvest() { - // Returns the harvest reward - // Called when player walks over filled neutral return this.harvestReward; } - - update(deltaTime) { - // Base update - override in subclasses - } - - draw(ctx, cameraX, cameraY) { - // Base draw - override in subclasses - } } ``` -## Alignment Tracking +## Garden Focus Tracking ```javascript -// js/systems/AlignmentSystem.js -export class AlignmentSystem { +// js/systems/GardenFocusSystem.js +export class GardenFocusSystem { constructor(gameState) { this.gameState = gameState; - this.alignment = { - arcane: 33.33, - primal: 33.33, - forged: 33.34 + this.focus = { + flowers: 33.33, + vegetables: 33.33, + trees: 33.34 }; } - harvestNeutral(neutralType) { + harvestPlant(plantType) { const harvestValue = 5; - switch(neutralType) { - case 'arcane': - this.alignment.arcane += harvestValue; + switch(plantType) { + case 'flowers': + this.focus.flowers += harvestValue; break; - case 'primal': - this.alignment.primal += harvestValue; + case 'vegetables': + this.focus.vegetables += harvestValue; break; - case 'forged': - this.alignment.forged += harvestValue; + case 'trees': + this.focus.trees += harvestValue; break; } - this.normalizeAlignment(); - return this.alignment; + this.normalizeFocus(); + return this.focus; } - normalizeAlignment() { - const total = this.alignment.arcane + - this.alignment.primal + - this.alignment.forged; + normalizeFocus() { + const total = this.focus.flowers + + this.focus.vegetables + + this.focus.trees; - this.alignment.arcane = (this.alignment.arcane / total) * 100; - this.alignment.primal = (this.alignment.primal / total) * 100; - this.alignment.forged = (this.alignment.forged / total) * 100; + this.focus.flowers = (this.focus.flowers / total) * 100; + this.focus.vegetables = (this.focus.vegetables / total) * 100; + this.focus.trees = (this.focus.trees / total) * 100; } - getAlignment() { - return { ...this.alignment }; + getFocus() { + return { ...this.focus }; } - getDominantAlignment() { - const { arcane, primal, forged } = this.alignment; - if (arcane >= primal && arcane >= forged) return 'arcane'; - if (primal >= arcane && primal >= forged) return 'primal'; - return 'forged'; + getDominantFocus() { + const { flowers, vegetables, trees } = this.focus; + if (flowers >= vegetables && flowers >= trees) return 'flowers'; + if (vegetables >= flowers && vegetables >= trees) return 'vegetables'; + return 'trees'; } } ``` -## Spawner Updates - -```javascript -// Updates to js/game/Spawner.js -import { CrystalNode } from '../entities/neutrals/CrystalNode.js'; -import { SeedPod } from '../entities/neutrals/SeedPod.js'; -import { OreVein } from '../entities/neutrals/OreVein.js'; - -// In spawnNeutral method: -spawnNeutral(game) { - // Random type selection (equal probability for now) - const roll = Math.random(); - let neutral; - - const spawnPos = this.getSpawnPosition(game); - - if (roll < 0.33) { - neutral = new CrystalNode(spawnPos.x, spawnPos.y); - } else if (roll < 0.66) { - neutral = new SeedPod(spawnPos.x, spawnPos.y); - } else { - neutral = new OreVein(spawnPos.x, spawnPos.y); - } - - game.neutralEntities.push(neutral); -} -``` - ## UI Display -Add alignment display to the info panel: - ```javascript -// In Renderer.js or a new UI component -drawAlignmentUI(ctx, alignment) { +// Garden focus bar with plant icons +drawGardenFocusUI(ctx, focus) { const x = 10; const y = 150; const barWidth = 150; @@ -338,26 +293,26 @@ drawAlignmentUI(ctx, alignment) { ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(x, y, barWidth, barHeight); - // Arcane segment (blue) - ctx.fillStyle = '#00aaff'; - ctx.fillRect(x, y, barWidth * (alignment.arcane / 100), barHeight); + // Flower segment (pink) + ctx.fillStyle = '#ff88cc'; + ctx.fillRect(x, y, barWidth * (focus.flowers / 100), barHeight); - // Primal segment (green) + // Vegetable segment (green) ctx.fillStyle = '#44aa44'; - const primalStart = barWidth * (alignment.arcane / 100); - ctx.fillRect(x + primalStart, y, barWidth * (alignment.primal / 100), barHeight); + const vegStart = barWidth * (focus.flowers / 100); + ctx.fillRect(x + vegStart, y, barWidth * (focus.vegetables / 100), barHeight); - // Forged segment (orange) - ctx.fillStyle = '#ff8844'; - const forgedStart = primalStart + barWidth * (alignment.primal / 100); - ctx.fillRect(x + forgedStart, y, barWidth * (alignment.forged / 100), barHeight); + // Tree segment (brown) + ctx.fillStyle = '#8B4513'; + const treeStart = vegStart + barWidth * (focus.vegetables / 100); + ctx.fillRect(x + treeStart, y, barWidth * (focus.trees / 100), barHeight); - // Labels + // Labels with emoji ctx.fillStyle = '#ffffff'; ctx.font = '12px Arial'; - ctx.fillText(`A: ${alignment.arcane.toFixed(0)}%`, x + 5, y + 14); - ctx.fillText(`P: ${alignment.primal.toFixed(0)}%`, x + 55, y + 14); - ctx.fillText(`F: ${alignment.forged.toFixed(0)}%`, x + 105, y + 14); + ctx.fillText(`🌸 ${focus.flowers.toFixed(0)}%`, x + 5, y + 14); + ctx.fillText(`🥕 ${focus.vegetables.toFixed(0)}%`, x + 55, y + 14); + ctx.fillText(`🌳 ${focus.trees.toFixed(0)}%`, x + 105, y + 14); } ``` @@ -371,34 +326,32 @@ vampire-survivor/js/ │ ├── HeavyEnemy.js │ ├── Projectile.js │ ├── WeaponPickup.js -│ └── neutrals/ -│ ├── NeutralEntity.js # Refactored base class -│ ├── CrystalNode.js # NEW -│ ├── SeedPod.js # NEW -│ └── OreVein.js # NEW +│ └── plants/ +│ ├── Plant.js # Base class +│ ├── FlowerBud.js # NEW +│ ├── VeggieSprout.js # NEW +│ └── Sapling.js # NEW ├── systems/ -│ └── AlignmentSystem.js # NEW +│ └── GardenFocusSystem.js # NEW ├── game/ -│ ├── CollisionSystem.js -│ ├── GameState.js # Add alignment state -│ ├── Renderer.js # Add alignment UI -│ ├── Spawner.js # Update for neutral types -│ └── TargetingSystem.js +│ ├── GameState.js # Add focus state +│ ├── Renderer.js # Add focus UI +│ ├── Spawner.js # Update for plant types +│ └── ... └── config/ - └── CollisionConfig.js + └── ... ``` ## Testing Checklist -- [ ] All three neutral types spawn correctly +- [ ] All three plant types spawn correctly - [ ] Each type has distinct visual appearance -- [ ] Crystal Node: stationary, pulsing glow -- [ ] Seed Pod: drifts slowly, breathing animation -- [ ] Ore Vein: stationary, heavy, cracks when filled -- [ ] Harvesting updates alignment percentages -- [ ] Alignment UI displays correctly -- [ ] Alignment always sums to 100% -- [ ] Collision physics work for all types +- [ ] Flower Bud: petals unfurl, butterflies when full +- [ ] Veggie Sprout: grows taller, vegetable appears +- [ ] Sapling: trunk thickens, canopy grows +- [ ] Harvesting updates garden focus percentages +- [ ] Focus UI displays correctly with plant icons +- [ ] Focus always sums to 100% ## Dependencies @@ -406,4 +359,4 @@ vampire-survivor/js/ ## Next Phase -[Phase 2: Enemy Type System](./02-phase-enemy-types.md) - Create alignment-specific enemy types with resistance/weakness system. +[Phase 2: Pest System](./02-phase-enemy-types.md) - Create garden pests attracted to different plant types. diff --git a/plans/world-build-system/02-phase-enemy-types.md b/plans/world-build-system/02-phase-enemy-types.md index ef83ca4..3648d38 100644 --- a/plans/world-build-system/02-phase-enemy-types.md +++ b/plans/world-build-system/02-phase-enemy-types.md @@ -1,172 +1,101 @@ -# Phase 2: Enemy Type System +# Phase 2: Pest System ## Overview -Create three categories of enemies that are attracted by different alignment types. Each enemy type has resistances and weaknesses, creating strategic depth in build choices. +Create three categories of garden pests attracted by different plant types. Each pest type has resistances and weaknesses, creating strategic depth in garden composition choices. ## Goals -- [ ] Create enemy base class with damage type support -- [ ] Implement Arcane-attracted enemies (2-3 types) -- [ ] Implement Primal-attracted enemies (2-3 types) -- [ ] Implement Forged-attracted enemies (2-3 types) +- [ ] Create Pest base class with damage type support +- [ ] Implement Flower-attracted pests (3 types) +- [ ] Implement Vegetable-attracted pests (3 types) +- [ ] Implement Tree-attracted pests (3 types) - [ ] Add resistance/weakness damage calculation -- [ ] Implement alignment-weighted spawn system -- [ ] Update UI to show enemy type distribution +- [ ] Implement focus-weighted spawn system +- [ ] Update UI to show pest distribution ## Damage Type System ### Resistance/Weakness Values -| Damage vs Enemy | Resistant | Neutral | Weak | -|-----------------|-----------|---------|------| +| Damage vs Pest | Resistant | Neutral | Weak | +|----------------|-----------|---------|------| | Multiplier | 0.5x | 1.0x | 1.5x | ### Type Relationships ``` -Arcane enemies: Resist Arcane, Weak to Primal & Forged -Primal enemies: Resist Primal, Weak to Arcane & Forged -Forged enemies: Resist Forged, Weak to Arcane & Primal +Flower pests: Resist Flower damage, Weak to Vegetable & Tree +Vegetable pests: Resist Vegetable damage, Weak to Flower & Tree +Tree pests: Resist Tree damage, Weak to Flower & Vegetable ``` -## Enemy Base Class Update +## Pest Categories -```javascript -// js/entities/enemies/EnemyBase.js -import { BaseEntity } from '../BaseEntity.js'; - -export class EnemyBase extends BaseEntity { - constructor(x, y, size) { - super(x, y, size); - this.enemyType = 'neutral'; // 'arcane', 'primal', 'forged' - this.health = 3; - this.maxHealth = 3; - this.speed = 150; - this.damage = 1; - - // Damage resistances - this.resistances = { - arcane: 1.0, - primal: 1.0, - forged: 1.0 - }; - } - - takeDamage(amount, damageType = 'neutral') { - const multiplier = this.resistances[damageType] || 1.0; - const actualDamage = amount * multiplier; - this.health -= actualDamage; - - // Visual feedback for resistance/weakness - if (multiplier < 1.0) { - this.showResistEffect(); - } else if (multiplier > 1.0) { - this.showWeaknessEffect(); - } - - return this.health <= 0; - } - - showResistEffect() { - // Flash with shield icon or "RESIST" text - } - - showWeaknessEffect() { - // Flash brighter, "WEAK" text, extra particles - } -} -``` +### Flower-Attracted Pests + +| Pest | Size | Health | Speed | Behavior | +|------|------|--------|-------|----------| +| **Aphid** | 15 | 1 | 200 | Swarms in clusters, very fast | +| **Moth** | 25 | 2 | 160 | Fluttery erratic movement | +| **Japanese Beetle** | 30 | 4 | 100 | Slow but armored | + +### Vegetable-Attracted Pests + +| Pest | Size | Health | Speed | Behavior | +|------|------|--------|-------|----------| +| **Caterpillar** | 20 | 2 | 80 | Slow inchworm crawl | +| **Rabbit** | 35 | 3 | 180 | Hops toward target | +| **Groundhog** | 45 | 5 | 120 | Can burrow underground | + +### Tree-Attracted Pests + +| Pest | Size | Health | Speed | Behavior | +|------|------|--------|-------|----------| +| **Bark Beetle** | 18 | 2 | 90 | Slow, hard shell | +| **Woodpecker** | 30 | 3 | 150 | Undulating flight | +| **Termite** | 12 | 1 | 100 | Swarms like aphids | -## Arcane-Attracted Enemies - -### Mana Leech -- **Size**: 20 (small) -- **Health**: 2 -- **Speed**: 180 (fast) -- **Behavior**: Erratic jittery movement -- **Visual**: Purple/blue wispy creature with trailing particles - -### Arcane Wraith -- **Size**: 30 (medium) -- **Health**: 4 -- **Speed**: 120 (medium) -- **Behavior**: Periodically phases (becomes semi-transparent) -- **Visual**: Ghostly figure, fades in and out - -## Primal-Attracted Enemies - -### Swarm Insect -- **Size**: 12 (very small) -- **Health**: 1 -- **Speed**: 200 (very fast) -- **Behavior**: Moves in swarm patterns with offset from player -- **Visual**: Small bug/insect, spawns in groups - -### Feral Beast -- **Size**: 35 (medium-large) -- **Health**: 3 -- **Speed**: 170 (fast), 350 when charging -- **Behavior**: Charges at player when close -- **Visual**: Wolf/beast shape, glows red when charging - -## Forged-Attracted Enemies - -### Rust Crawler -- **Size**: 25 (medium) -- **Health**: 4 -- **Speed**: 100 (slow) -- **Behavior**: Steady movement, leaves rust trail -- **Visual**: Mechanical spider/crawler, rust-colored - -### Scrap Golem -- **Size**: 50 (large) -- **Health**: 8 -- **Speed**: 80 (very slow) -- **Behavior**: Slow but tanky, high damage -- **Visual**: Large construct made of scrap metal - -## Alignment-Weighted Spawn System +## Focus-Weighted Spawn System ```javascript -// js/systems/EnemySpawnSystem.js -export class EnemySpawnSystem { - constructor(alignmentSystem) { - this.alignmentSystem = alignmentSystem; - this.enemyTypes = { - arcane: [ManaLeech, ArcaneWraith], - primal: [SwarmInsect, FeralBeast], - forged: [RustCrawler, ScrapGolem] +// js/systems/PestSpawnSystem.js +export class PestSpawnSystem { + constructor(gardenFocusSystem) { + this.gardenFocusSystem = gardenFocusSystem; + this.pestTypes = { + flowers: [Aphid, Moth, JapaneseBeetle], + vegetables: [Caterpillar, Rabbit, Groundhog], + trees: [BarkBeetle, Woodpecker, Termite] }; } getSpawnWeights() { - const alignment = this.alignmentSystem.getAlignment(); + const focus = this.gardenFocusSystem.getFocus(); return { - arcane: alignment.arcane / 100, - primal: alignment.primal / 100, - forged: alignment.forged / 100 + flowers: focus.flowers / 100, + vegetables: focus.vegetables / 100, + trees: focus.trees / 100 }; } - spawnEnemy(x, y) { + spawnPest(x, y) { const weights = this.getSpawnWeights(); const roll = Math.random(); let category; - if (roll < weights.arcane) { - category = 'arcane'; - } else if (roll < weights.arcane + weights.primal) { - category = 'primal'; + if (roll < weights.flowers) { + category = 'flowers'; + } else if (roll < weights.flowers + weights.vegetables) { + category = 'vegetables'; } else { - category = 'forged'; + category = 'trees'; } - // Random enemy from category - const types = this.enemyTypes[category]; - const EnemyClass = types[Math.floor(Math.random() * types.length)]; + const types = this.pestTypes[category]; + const PestClass = types[Math.floor(Math.random() * types.length)]; - return new EnemyClass(x, y); + return new PestClass(x, y); } } ``` @@ -176,38 +105,41 @@ export class EnemySpawnSystem { ``` vampire-survivor/js/ ├── entities/ -│ ├── enemies/ -│ │ ├── EnemyBase.js # NEW - base class with resistances -│ │ ├── arcane/ -│ │ │ ├── ManaLeech.js # NEW -│ │ │ └── ArcaneWraith.js # NEW -│ │ ├── primal/ -│ │ │ ├── SwarmInsect.js # NEW -│ │ │ └── FeralBeast.js # NEW -│ │ └── forged/ -│ │ ├── RustCrawler.js # NEW -│ │ └── ScrapGolem.js # NEW -│ └── neutrals/ +│ ├── pests/ +│ │ ├── Pest.js # Base class +│ │ ├── flower/ +│ │ │ ├── Aphid.js +│ │ │ ├── Moth.js +│ │ │ └── JapaneseBeetle.js +│ │ ├── vegetable/ +│ │ │ ├── Caterpillar.js +│ │ │ ├── Rabbit.js +│ │ │ └── Groundhog.js +│ │ └── tree/ +│ │ ├── BarkBeetle.js +│ │ ├── Woodpecker.js +│ │ └── Termite.js +│ └── plants/ │ └── ... ├── systems/ -│ ├── AlignmentSystem.js -│ └── EnemySpawnSystem.js # NEW +│ ├── GardenFocusSystem.js +│ └── PestSpawnSystem.js # NEW └── ... ``` ## Testing Checklist -- [ ] All enemy types spawn correctly -- [ ] Each enemy has distinct visual appearance +- [ ] All pest types spawn correctly +- [ ] Each pest has distinct visual appearance - [ ] Resistance/weakness damage multipliers work - [ ] Visual feedback shows for resist/weak hits -- [ ] Spawn weights match alignment percentages -- [ ] Enemy behaviors work as designed +- [ ] Spawn weights match garden focus percentages +- [ ] Pest behaviors work as designed ## Dependencies -- Phase 1: Multiple Neutral Types (for alignment tracking) +- Phase 1: Plant Types (for garden focus tracking) ## Next Phase -[Phase 3: Alignment Effects](./03-phase-alignment-effects.md) - Implement terrain features and world changes based on alignment thresholds. +[Phase 3: Garden Terrain](./03-phase-alignment-effects.md) - Implement terrain features based on garden focus. diff --git a/plans/world-build-system/03-phase-alignment-effects.md b/plans/world-build-system/03-phase-alignment-effects.md index d8fe2b3..950e1a3 100644 --- a/plans/world-build-system/03-phase-alignment-effects.md +++ b/plans/world-build-system/03-phase-alignment-effects.md @@ -1,37 +1,37 @@ -# Phase 3: Alignment Effects +# Phase 3: Garden Terrain Features ## Overview -Implement terrain features and visual world changes that appear based on alignment thresholds. The world should visibly transform as players commit to an alignment path. +Implement terrain features that develop naturally based on garden focus. The garden should visibly transform as players commit to a plant type. ## Goals -- [ ] Implement alignment threshold system (40%, 60%, 80%) -- [ ] Create Arcane terrain features (energy pools, ley lines) -- [ ] Create Primal terrain features (vegetation, thorns) -- [ ] Create Forged terrain features (metal plates, turret nodes) -- [ ] Add visual world tinting based on dominant alignment -- [ ] Implement anchor point system (Ley Points, Growth Nodes, Forge Points) +- [ ] Implement garden focus threshold system (40%, 60%, 80%) +- [ ] Create Flower terrain features (pollen clouds, butterfly paths) +- [ ] Create Vegetable terrain features (compost piles, scarecrows) +- [ ] Create Tree terrain features (root networks, bird houses) +- [ ] Add visual garden tinting based on dominant focus +- [ ] Implement harvest point system -## Alignment Thresholds +## Garden Focus Thresholds | Threshold | Effect | |-----------|--------| | 40% | Minor terrain features begin appearing | | 60% | Major terrain features, strong visual shift | -| 80% | Dominant alignment, maximum effects | +| 80% | Dominant garden, maximum effects | ## Terrain Feature Base Class ```javascript -// js/entities/terrain/TerrainFeature.js -export class TerrainFeature { +// js/entities/terrain/GardenFeature.js +export class GardenFeature { constructor(x, y, radius) { this.x = x; this.y = y; this.radius = radius; this.active = true; - this.alignmentType = 'neutral'; + this.focusType = 'neutral'; } isEntityInRange(entity) { @@ -43,349 +43,261 @@ export class TerrainFeature { applyEffect(entity, deltaTime) { // Override in subclasses } - - update(deltaTime) { - // Override in subclasses - } - - draw(ctx, cameraX, cameraY) { - // Override in subclasses - } } ``` -## Arcane Terrain Features +## Flower Terrain Features -### Energy Pool -- **Appearance**: Glowing blue circle on ground -- **Effect**: Damages enemies standing in it (2 DPS) -- **Spawns**: Near Ley Points when Arcane > 40% +### Pollen Cloud +- **Appearance**: Floating yellow/golden particles +- **Effect**: Confuses pests (random movement), slows them 30% +- **Spawns**: Near harvested flower locations when Flowers > 40% ```javascript -// js/entities/terrain/arcane/EnergyPool.js -export class EnergyPool extends TerrainFeature { +// js/entities/terrain/flower/PollenCloud.js +export class PollenCloud extends GardenFeature { constructor(x, y) { - super(x, y, 60); - this.alignmentType = 'arcane'; - this.damagePerSecond = 2; - this.pulsePhase = 0; + super(x, y, 70); + this.focusType = 'flowers'; + this.slowAmount = 0.3; + this.confuseChance = 0.1; // per second + this.particles = []; + this.driftPhase = 0; } applyEffect(entity, deltaTime) { - if (entity.enemyType && this.isEntityInRange(entity)) { - entity.takeDamage(this.damagePerSecond * deltaTime, 'arcane'); + if (entity.pestType && this.isEntityInRange(entity)) { + // Slow pests + entity.speedMultiplier = 1 - this.slowAmount; + + // Chance to confuse (random direction) + if (Math.random() < this.confuseChance * deltaTime) { + entity.confusedTimer = 1.0; + } } } draw(ctx, cameraX, cameraY) { - const screenX = this.x - cameraX; - const screenY = this.y - cameraY; - - // Pulsing glow effect - this.pulsePhase += 0.05; - const alpha = 0.3 + 0.2 * Math.sin(this.pulsePhase); - - ctx.beginPath(); - ctx.arc(screenX, screenY, this.radius, 0, Math.PI * 2); - ctx.fillStyle = `rgba(0, 170, 255, ${alpha})`; - ctx.fill(); + // Draw floating pollen particles + // Yellow/golden color, drifting motion } } ``` -### Ley Line -- **Appearance**: Glowing line connecting two Ley Points -- **Effect**: Boosts Arcane damage for player in area (+25%) -- **Spawns**: Automatically connects nearby Ley Points +### Butterfly Path +- **Appearance**: Trail of butterflies between flower harvest points +- **Effect**: Player moves 20% faster along path, +25% flower damage +- **Spawns**: Connects nearby flower harvest points ```javascript -// js/entities/terrain/arcane/LeyLine.js -export class LeyLine extends TerrainFeature { +// js/entities/terrain/flower/ButterflyPath.js +export class ButterflyPath extends GardenFeature { constructor(point1, point2) { const midX = (point1.x + point2.x) / 2; const midY = (point1.y + point2.y) / 2; - super(midX, midY, 30); // Width of effect area + super(midX, midY, 40); this.point1 = point1; this.point2 = point2; - this.alignmentType = 'arcane'; + this.focusType = 'flowers'; + this.speedBoost = 0.2; this.damageBoost = 0.25; - } - - isEntityInRange(entity) { - // Check if entity is near the line segment - return this.distanceToLine(entity.x, entity.y) <= this.radius; - } - - distanceToLine(px, py) { - // Calculate perpendicular distance to line segment - const dx = this.point2.x - this.point1.x; - const dy = this.point2.y - this.point1.y; - const length = Math.sqrt(dx * dx + dy * dy); - - if (length === 0) return Math.sqrt((px - this.point1.x) ** 2 + (py - this.point1.y) ** 2); - - const t = Math.max(0, Math.min(1, ((px - this.point1.x) * dx + (py - this.point1.y) * dy) / (length * length))); - const nearestX = this.point1.x + t * dx; - const nearestY = this.point1.y + t * dy; - - return Math.sqrt((px - nearestX) ** 2 + (py - nearestY) ** 2); + this.butterflies = []; } draw(ctx, cameraX, cameraY) { - const x1 = this.point1.x - cameraX; - const y1 = this.point1.y - cameraY; - const x2 = this.point2.x - cameraX; - const y2 = this.point2.y - cameraY; - - // Glowing line - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.strokeStyle = 'rgba(0, 200, 255, 0.6)'; - ctx.lineWidth = 8; - ctx.stroke(); - - // Inner bright line - ctx.strokeStyle = 'rgba(150, 230, 255, 0.8)'; - ctx.lineWidth = 3; - ctx.stroke(); + // Draw butterflies flying along path + // Colorful wings, gentle floating motion } } ``` -## Primal Terrain Features +## Vegetable Terrain Features -### Vegetation Patch -- **Appearance**: Green grass/plant area -- **Effect**: Heals player (1 HP/sec), slows enemies by 20% -- **Spawns**: Near Growth Nodes when Primal > 40% +### Compost Pile +- **Appearance**: Brown/green mound with steam rising +- **Effect**: Heals player 2 HP/sec, speeds nearby plant growth +- **Spawns**: Near vegetable harvest points when Vegetables > 40% ```javascript -// js/entities/terrain/primal/VegetationPatch.js -export class VegetationPatch extends TerrainFeature { +// js/entities/terrain/vegetable/CompostPile.js +export class CompostPile extends GardenFeature { constructor(x, y) { - super(x, y, 80); - this.alignmentType = 'primal'; - this.healPerSecond = 1; - this.slowAmount = 0.2; - this.swayPhase = Math.random() * Math.PI * 2; + super(x, y, 60); + this.focusType = 'vegetables'; + this.healPerSecond = 2; + this.growthBoost = 1.5; + this.steamParticles = []; } applyEffect(entity, deltaTime) { if (this.isEntityInRange(entity)) { if (entity.isPlayer) { entity.heal(this.healPerSecond * deltaTime); - } else if (entity.enemyType) { - entity.speedMultiplier = 1 - this.slowAmount; + } + if (entity.type === 'plant') { + entity.growthMultiplier = this.growthBoost; } } } draw(ctx, cameraX, cameraY) { - const screenX = this.x - cameraX; - const screenY = this.y - cameraY; - - // Draw grass patch - ctx.beginPath(); - ctx.arc(screenX, screenY, this.radius, 0, Math.PI * 2); - ctx.fillStyle = 'rgba(60, 140, 60, 0.4)'; - ctx.fill(); - - // Draw individual grass blades (simplified) - this.swayPhase += 0.02; - // ... grass blade drawing + // Draw compost mound + // Rising steam particles + // Earthy brown/green colors } } ``` -### Thorn Barrier -- **Appearance**: Thorny vine wall -- **Effect**: Damages enemies passing through (3 damage) -- **Spawns**: At edges of vegetation areas when Primal > 60% - -## Forged Terrain Features - -### Metal Plate -- **Appearance**: Industrial metal floor section -- **Effect**: Slows enemies by 30%, player moves normally -- **Spawns**: Near Forge Points when Forged > 40% +### Scarecrow +- **Appearance**: Classic scarecrow on post +- **Effect**: Pests avoid area (fear radius), reduced spawn nearby +- **Spawns**: At vegetable harvest points when Vegetables > 60% ```javascript -// js/entities/terrain/forged/MetalPlate.js -export class MetalPlate extends TerrainFeature { +// js/entities/terrain/vegetable/Scarecrow.js +export class Scarecrow extends GardenFeature { constructor(x, y) { - super(x, y, 70); - this.alignmentType = 'forged'; - this.enemySlowAmount = 0.3; + super(x, y, 100); + this.focusType = 'vegetables'; + this.fearRadius = 100; + this.swayPhase = 0; } applyEffect(entity, deltaTime) { - if (this.isEntityInRange(entity) && entity.enemyType) { - entity.speedMultiplier = 1 - this.enemySlowAmount; + if (entity.pestType && this.isEntityInRange(entity)) { + // Push pests away from scarecrow + const dx = entity.x - this.x; + const dy = entity.y - this.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist > 0 && dist < this.fearRadius) { + const pushStrength = (1 - dist / this.fearRadius) * 50; + entity.vx += (dx / dist) * pushStrength * deltaTime; + entity.vy += (dy / dist) * pushStrength * deltaTime; + } } } draw(ctx, cameraX, cameraY) { - const screenX = this.x - cameraX; - const screenY = this.y - cameraY; - - // Draw metal plate (hexagonal or rectangular) - ctx.fillStyle = 'rgba(100, 100, 110, 0.6)'; - ctx.fillRect(screenX - this.radius, screenY - this.radius, - this.radius * 2, this.radius * 2); - - // Metallic highlights - ctx.strokeStyle = 'rgba(150, 150, 160, 0.8)'; - ctx.lineWidth = 2; - ctx.strokeRect(screenX - this.radius, screenY - this.radius, - this.radius * 2, this.radius * 2); + // Draw scarecrow figure + // Gentle swaying animation + // Straw hat, plaid shirt } } ``` -### Turret Node -- **Appearance**: Small automated turret -- **Effect**: Auto-attacks nearby enemies (1 damage/sec) -- **Spawns**: Near Forge Points when Forged > 60% - -## Anchor Point System +## Tree Terrain Features -When neutrals are harvested, they leave behind anchor points: +### Root Network +- **Appearance**: Visible roots spreading across ground +- **Effect**: Trips pests (50% slow), player unaffected +- **Spawns**: Near tree harvest points when Trees > 40% ```javascript -// js/systems/AnchorPointSystem.js -export class AnchorPointSystem { - constructor() { - this.leyPoints = []; // From Crystal harvests - this.growthNodes = []; // From Seed harvests - this.forgePoints = []; // From Ore harvests +// js/entities/terrain/tree/RootNetwork.js +export class RootNetwork extends GardenFeature { + constructor(x, y) { + super(x, y, 80); + this.focusType = 'trees'; + this.slowAmount = 0.5; + this.rootPaths = this.generateRoots(); } - addAnchorPoint(type, x, y) { - const point = { x, y, createdAt: Date.now() }; - - switch(type) { - case 'arcane': - this.leyPoints.push(point); - break; - case 'primal': - this.growthNodes.push(point); - break; - case 'forged': - this.forgePoints.push(point); - break; + generateRoots() { + // Generate branching root pattern + const roots = []; + for (let i = 0; i < 5; i++) { + const angle = (i / 5) * Math.PI * 2; + roots.push({ + angle, + length: 40 + Math.random() * 40, + branches: Math.floor(Math.random() * 3) + }); } + return roots; } - getPointsOfType(type) { - switch(type) { - case 'arcane': return this.leyPoints; - case 'primal': return this.growthNodes; - case 'forged': return this.forgePoints; - default: return []; + applyEffect(entity, deltaTime) { + if (entity.pestType && this.isEntityInRange(entity)) { + entity.speedMultiplier = 1 - this.slowAmount; } } + + draw(ctx, cameraX, cameraY) { + // Draw branching root pattern + // Brown/tan colors + // Organic, twisting shapes + } } ``` -## Terrain System +### Bird House +- **Appearance**: Small wooden house on pole +- **Effect**: Birds attack nearby pests (2 damage/sec) +- **Spawns**: At tree harvest points when Trees > 60% ```javascript -// js/systems/TerrainSystem.js -export class TerrainSystem { - constructor(alignmentSystem, anchorPointSystem) { - this.alignmentSystem = alignmentSystem; - this.anchorPointSystem = anchorPointSystem; - this.terrainFeatures = []; - this.spawnCheckInterval = 2; // seconds - this.timeSinceLastCheck = 0; +// js/entities/terrain/tree/BirdHouse.js +export class BirdHouse extends GardenFeature { + constructor(x, y) { + super(x, y, 120); + this.focusType = 'trees'; + this.attackDamage = 2; + this.attackInterval = 1.0; + this.timeSinceAttack = 0; + this.birds = []; } - update(deltaTime, entities) { - // Check for new terrain spawns - this.timeSinceLastCheck += deltaTime; - if (this.timeSinceLastCheck >= this.spawnCheckInterval) { - this.checkTerrainSpawns(); - this.timeSinceLastCheck = 0; - } + update(deltaTime, pests) { + this.timeSinceAttack += deltaTime; - // Update existing terrain - for (const terrain of this.terrainFeatures) { - terrain.update(deltaTime); - - // Apply effects to entities - for (const entity of entities) { - terrain.applyEffect(entity, deltaTime); + if (this.timeSinceAttack >= this.attackInterval) { + // Find nearest pest in range + const target = this.findNearestPest(pests); + if (target) { + this.launchBird(target); + this.timeSinceAttack = 0; } } - } - - checkTerrainSpawns() { - const alignment = this.alignmentSystem.getAlignment(); - - // Arcane terrain at 40%+ - if (alignment.arcane >= 40) { - this.trySpawnArcaneFeatures(); - } - - // Primal terrain at 40%+ - if (alignment.primal >= 40) { - this.trySpawnPrimalFeatures(); - } - // Forged terrain at 40%+ - if (alignment.forged >= 40) { - this.trySpawnForgedFeatures(); - } + // Update bird animations + this.updateBirds(deltaTime); } - trySpawnArcaneFeatures() { - const leyPoints = this.anchorPointSystem.getPointsOfType('arcane'); - - // Spawn energy pools near ley points - for (const point of leyPoints) { - if (Math.random() < 0.1 && !this.hasFeatureNear(point.x, point.y, 100)) { - this.terrainFeatures.push(new EnergyPool( - point.x + (Math.random() - 0.5) * 100, - point.y + (Math.random() - 0.5) * 100 - )); - } - } - - // Connect nearby ley points with ley lines - this.tryCreateLeyLines(leyPoints); + launchBird(target) { + this.birds.push({ + x: this.x, + y: this.y - 30, + target, + speed: 200 + }); } - hasFeatureNear(x, y, distance) { - return this.terrainFeatures.some(f => { - const dx = f.x - x; - const dy = f.y - y; - return Math.sqrt(dx * dx + dy * dy) < distance; - }); + draw(ctx, cameraX, cameraY) { + // Draw bird house + // Draw flying birds attacking pests } } ``` -## World Visual Tinting +## Garden Visual Tinting ```javascript // In Renderer.js -drawWorldTint(ctx, alignment, canvasWidth, canvasHeight) { - const dominant = this.getDominantAlignment(alignment); +drawGardenTint(ctx, focus, canvasWidth, canvasHeight) { let tintColor; let tintAlpha = 0; - // Calculate tint based on dominant alignment - if (alignment.arcane >= 40) { - tintColor = '0, 100, 200'; - tintAlpha = (alignment.arcane - 40) / 100 * 0.15; - } else if (alignment.primal >= 40) { - tintColor = '50, 150, 50'; - tintAlpha = (alignment.primal - 40) / 100 * 0.15; - } else if (alignment.forged >= 40) { - tintColor = '150, 100, 50'; - tintAlpha = (alignment.forged - 40) / 100 * 0.15; + if (focus.flowers >= 40) { + tintColor = '255, 150, 200'; // Pink + tintAlpha = (focus.flowers - 40) / 100 * 0.12; + } else if (focus.vegetables >= 40) { + tintColor = '100, 180, 100'; // Green + tintAlpha = (focus.vegetables - 40) / 100 * 0.12; + } else if (focus.trees >= 40) { + tintColor = '139, 90, 43'; // Brown + tintAlpha = (focus.trees - 40) / 100 * 0.12; } if (tintAlpha > 0) { @@ -401,39 +313,40 @@ drawWorldTint(ctx, alignment, canvasWidth, canvasHeight) { vampire-survivor/js/ ├── entities/ │ ├── terrain/ -│ │ ├── TerrainFeature.js # NEW - base class -│ │ ├── arcane/ -│ │ │ ├── EnergyPool.js # NEW -│ │ │ └── LeyLine.js # NEW -│ │ ├── primal/ -│ │ │ ├── VegetationPatch.js # NEW -│ │ │ └── ThornBarrier.js # NEW -│ │ └── forged/ -│ │ ├── MetalPlate.js # NEW -│ │ └── TurretNode.js # NEW +│ │ ├── GardenFeature.js # Base class +│ │ ├── flower/ +│ │ │ ├── PollenCloud.js +│ │ │ └── ButterflyPath.js +│ │ ├── vegetable/ +│ │ │ ├── CompostPile.js +│ │ │ └── Scarecrow.js +│ │ └── tree/ +│ │ ├── RootNetwork.js +│ │ └── BirdHouse.js ├── systems/ -│ ├── AlignmentSystem.js -│ ├── EnemySpawnSystem.js -│ ├── AnchorPointSystem.js # NEW +│ ├── GardenFocusSystem.js +│ ├── PestSpawnSystem.js +│ ├── HarvestPointSystem.js # NEW │ └── TerrainSystem.js # NEW └── ... ``` ## Testing Checklist -- [ ] Terrain features spawn at correct alignment thresholds -- [ ] Energy pools damage enemies -- [ ] Ley lines connect nearby Ley Points -- [ ] Vegetation patches heal player and slow enemies -- [ ] Metal plates slow enemies -- [ ] World tint changes with dominant alignment -- [ ] Anchor points created on neutral harvest +- [ ] Terrain features spawn at correct focus thresholds +- [ ] Pollen clouds slow and confuse pests +- [ ] Butterfly paths boost player speed and damage +- [ ] Compost piles heal player +- [ ] Scarecrows repel pests +- [ ] Root networks slow pests +- [ ] Bird houses attack pests +- [ ] Garden tint changes with dominant focus ## Dependencies -- Phase 1: Multiple Neutral Types -- Phase 2: Enemy Type System +- Phase 1: Plant Types +- Phase 2: Pest System ## Next Phase -[Phase 4: Aspect System Foundation](./04-phase-aspect-foundation.md) - Create the Aspect ability system with basic abilities for each alignment. +[Phase 4: Gardener Abilities](./04-phase-aspect-foundation.md) - Create the ability system with garden-themed powers. diff --git a/plans/world-build-system/04-phase-aspect-foundation.md b/plans/world-build-system/04-phase-aspect-foundation.md index 814b100..af2737f 100644 --- a/plans/world-build-system/04-phase-aspect-foundation.md +++ b/plans/world-build-system/04-phase-aspect-foundation.md @@ -1,290 +1,108 @@ -# Phase 4: Aspect System Foundation +# Phase 4: Gardener Abilities Foundation ## Overview -Create the Aspect ability system - powerful abilities that drop from neutrals and scale with world alignment. This phase focuses on the foundation: base class, 3-4 aspects per alignment, and basic UI. +Create the Gardener Ability system - powerful skills that drop from harvested plants and scale with garden focus. ## Goals -- [ ] Create Aspect base class -- [ ] Implement 3-4 Arcane Aspects -- [ ] Implement 3-4 Primal Aspects -- [ ] Implement 3-4 Forged Aspects -- [ ] Add Aspect drop system to neutral harvesting -- [ ] Create Aspect inventory and equip system -- [ ] Build Aspect UI (equipped slots, inventory) - -## Aspect Base Class +- [ ] Create Ability base class +- [ ] Implement 4 Flower Abilities +- [ ] Implement 4 Vegetable Abilities +- [ ] Implement 4 Tree Abilities +- [ ] Add ability drop system to plant harvesting +- [ ] Create ability inventory and equip system +- [ ] Build ability UI + +## Ability Summary + +### Flower Abilities +| Ability | Type | Effect | +|---------|------|--------| +| **Fragrant Bloom** | Passive | +25% flower damage | +| **Pollen Burst** | On-hit | Spreads to 2 nearby pests | +| **Petal Shield** | Passive | 20 HP protective barrier | +| **Butterfly Wings** | Active | Dash between flower points | + +### Vegetable Abilities +| Ability | Type | Effect | +|---------|------|--------| +| **Nutrient Absorption** | Passive | +1 HP/sec healing | +| **Rapid Growth** | Passive | Plants fill 50% faster | +| **Garden Cat** | Passive | Cat companion hunts pests | +| **Vine Trap** | Passive | Vines slow pests 30% | + +### Tree Abilities +| Ability | Type | Effect | +|---------|------|--------| +| **Bark Armor** | Passive | +20% damage reduction | +| **Treant Sprout** | Passive | Small treant allies | +| **Sap Trap** | Active | Sticky sap immobilizes pests | +| **Bird Frenzy** | Passive | Bird houses attack 50% faster | + +## Ability Drop System ```javascript -// js/entities/aspects/Aspect.js -export class Aspect { +// js/systems/AbilityDropSystem.js +export class AbilityDropSystem { constructor() { - this.id = ''; - this.name = ''; - this.description = ''; - this.alignmentType = 'neutral'; // 'arcane', 'primal', 'forged' - this.rarity = 'common'; // 'common', 'uncommon', 'rare' - - // Effect values (base, before alignment scaling) - this.baseEffect = {}; - - // State - this.equipped = false; - this.cooldown = 0; - this.maxCooldown = 0; - } - - // Calculate effect with alignment bonus - getScaledEffect(alignmentSystem) { - const alignment = alignmentSystem.getAlignment(); - const matchingAlignment = alignment[this.alignmentType] || 33; - - // Base scaling: 1.0 at 33%, up to 2.0 at 100% - const scale = 1 + (matchingAlignment - 33) / 67; - - const scaledEffect = {}; - for (const [key, value] of Object.entries(this.baseEffect)) { - scaledEffect[key] = value * scale; - } - - return scaledEffect; - } - - update(deltaTime, gameState, alignmentSystem) { - if (this.cooldown > 0) { - this.cooldown -= deltaTime; - } - } - - applyPassive(player, alignmentSystem) { - // Override in subclasses - } - - activate(player, gameState, alignmentSystem) { - // Override in subclasses - } - - canActivate() { - return this.cooldown <= 0; - } -} -``` - -## Arcane Aspects - -### Mana Surge -- **Effect**: +25% Arcane damage (scales to +50% at 100% Arcane) -- **Type**: Passive - -### Chain Lightning -- **Effect**: Attacks arc to 2 nearby enemies (scales to 4 at 100%) -- **Type**: Passive (triggers on hit) - -### Energy Shield -- **Effect**: 20 point damage shield, regenerates when not hit -- **Type**: Passive - -### Ley Walker -- **Effect**: Teleport to nearest Ley Point (active ability) -- **Type**: Active, 10 second cooldown -- **Rarity**: Rare - -## Primal Aspects - -### Regeneration -- **Effect**: +1 HP/sec passive healing (scales to +2 at 100%) -- **Type**: Passive - -### Overgrowth -- **Effect**: Enemies in vegetation take 2 DPS (scales to 4) -- **Type**: Passive (enhances terrain) - -### Beast Companion -- **Effect**: Summon wolf ally that attacks enemies -- **Type**: Passive (always active) - -### Root Snare -- **Effect**: Enemies near vegetation slowed 25% (scales to 50%) -- **Type**: Passive (enhances terrain) - -## Forged Aspects - -### Metal Skin -- **Effect**: +20% damage reduction (scales to +40%) -- **Type**: Passive - -### Construct Ally -- **Effect**: Forge Points spawn turret drones -- **Type**: Passive - -### Magnetize -- **Effect**: Pull enemies toward ore deposits -- **Type**: Active, 8 second cooldown - -### Overclock -- **Effect**: Turrets fire 25% faster (scales to 50%) -- **Type**: Passive (enhances terrain) - -## Aspect Drop System - -```javascript -// js/systems/AspectDropSystem.js -export class AspectDropSystem { - constructor() { - this.aspectPool = { - arcane: [ManaSurge, ChainLightning, EnergyShield, LeyWalker], - primal: [Regeneration, Overgrowth, BeastCompanion, RootSnare], - forged: [MetalSkin, ConstructAlly, Magnetize, Overclock] - }; - this.baseDropRate = 0.20; // 20% base chance } - rollForAspect(neutralType, fillLevel, maxFill) { - // Higher fill = higher drop chance + rollForAbility(plantType, fillLevel, maxFill) { const fillBonus = (fillLevel / maxFill) * 0.10; const dropChance = this.baseDropRate + fillBonus; if (Math.random() > dropChance) { - return null; // No drop + return null; } - // 80% chance matching type, 20% random - let aspectType = neutralType; + // 80% matching type, 20% random + let abilityType = plantType; if (Math.random() > 0.80) { - const types = ['arcane', 'primal', 'forged']; - aspectType = types[Math.floor(Math.random() * types.length)]; + const types = ['flowers', 'vegetables', 'trees']; + abilityType = types[Math.floor(Math.random() * types.length)]; } - // Pick random aspect from pool - const pool = this.aspectPool[aspectType]; - const AspectClass = pool[Math.floor(Math.random() * pool.length)]; - - return new AspectClass(); + return this.createAbility(abilityType); } } ``` -## Aspect Manager +## Ability Manager ```javascript -// js/systems/AspectManager.js -export class AspectManager { +// js/systems/AbilityManager.js +export class AbilityManager { constructor() { - this.equipped = []; // Currently equipped aspects - this.inventory = []; // Collected but not equipped - this.maxSlots = 3; // Starting slots + this.equipped = []; + this.inventory = []; + this.maxSlots = 3; this.maxInventory = 10; } - addAspect(aspect) { + addAbility(ability) { if (this.inventory.length < this.maxInventory) { - this.inventory.push(aspect); + this.inventory.push(ability); return true; } - return false; // Inventory full + return false; } - equipAspect(aspect) { + equipAbility(ability) { if (this.equipped.length >= this.maxSlots) { - return false; // No slots available + return false; } - const index = this.inventory.indexOf(aspect); + const index = this.inventory.indexOf(ability); if (index > -1) { this.inventory.splice(index, 1); - this.equipped.push(aspect); - aspect.equipped = true; - return true; - } - return false; - } - - unequipAspect(aspect) { - const index = this.equipped.indexOf(aspect); - if (index > -1) { - this.equipped.splice(index, 1); - this.inventory.push(aspect); - aspect.equipped = false; + this.equipped.push(ability); + ability.equipped = true; return true; } return false; } - - update(deltaTime, gameState, alignmentSystem) { - for (const aspect of this.equipped) { - aspect.update(deltaTime, gameState, alignmentSystem); - aspect.applyPassive(gameState.player, alignmentSystem); - } - } - - getEquippedOfType(alignmentType) { - return this.equipped.filter(a => a.alignmentType === alignmentType); - } -} -``` - -## Aspect UI - -```javascript -// js/ui/AspectUI.js -export class AspectUI { - constructor(aspectManager) { - this.aspectManager = aspectManager; - this.slotSize = 50; - this.padding = 10; - } - - draw(ctx, canvasWidth, canvasHeight, alignmentSystem) { - // Draw equipped slots at bottom of screen - const startX = canvasWidth / 2 - (this.aspectManager.maxSlots * (this.slotSize + this.padding)) / 2; - const y = canvasHeight - this.slotSize - 20; - - for (let i = 0; i < this.aspectManager.maxSlots; i++) { - const x = startX + i * (this.slotSize + this.padding); - - // Draw slot background - ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; - ctx.fillRect(x, y, this.slotSize, this.slotSize); - ctx.strokeStyle = '#666'; - ctx.strokeRect(x, y, this.slotSize, this.slotSize); - - // Draw aspect if equipped - const aspect = this.aspectManager.equipped[i]; - if (aspect) { - this.drawAspectIcon(ctx, aspect, x, y, alignmentSystem); - } - } - } - - drawAspectIcon(ctx, aspect, x, y, alignmentSystem) { - // Color based on alignment type - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - - ctx.fillStyle = colors[aspect.alignmentType] || '#888'; - ctx.fillRect(x + 5, y + 5, this.slotSize - 10, this.slotSize - 10); - - // Show cooldown overlay if on cooldown - if (aspect.cooldown > 0) { - const cooldownPercent = aspect.cooldown / aspect.maxCooldown; - ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; - ctx.fillRect(x + 5, y + 5, this.slotSize - 10, (this.slotSize - 10) * cooldownPercent); - } - - // Show alignment scaling indicator - const effect = aspect.getScaledEffect(alignmentSystem); - const scale = Object.values(effect)[0] / Object.values(aspect.baseEffect)[0]; - if (scale > 1.2) { - ctx.strokeStyle = '#ffff00'; - ctx.lineWidth = 2; - ctx.strokeRect(x + 3, y + 3, this.slotSize - 6, this.slotSize - 6); - } - } } ``` @@ -293,48 +111,44 @@ export class AspectUI { ``` vampire-survivor/js/ ├── entities/ -│ ├── aspects/ -│ │ ├── Aspect.js # NEW - base class -│ │ ├── arcane/ -│ │ │ ├── ManaSurge.js # NEW -│ │ │ ├── ChainLightning.js # NEW -│ │ │ ├── EnergyShield.js # NEW -│ │ │ └── LeyWalker.js # NEW -│ │ ├── primal/ -│ │ │ ├── Regeneration.js # NEW -│ │ │ ├── Overgrowth.js # NEW -│ │ │ ├── BeastCompanion.js # NEW -│ │ │ └── RootSnare.js # NEW -│ │ └── forged/ -│ │ ├── MetalSkin.js # NEW -│ │ ├── ConstructAlly.js # NEW -│ │ ├── Magnetize.js # NEW -│ │ └── Overclock.js # NEW +│ ├── abilities/ +│ │ ├── Ability.js +│ │ ├── flower/ +│ │ │ ├── FragrantBloom.js +│ │ │ ├── PollenBurst.js +│ │ │ ├── PetalShield.js +│ │ │ └── ButterflyWings.js +│ │ ├── vegetable/ +│ │ │ ├── NutrientAbsorption.js +│ │ │ ├── RapidGrowth.js +│ │ │ ├── GardenCat.js +│ │ │ └── VineTrap.js +│ │ └── tree/ +│ │ ├── BarkArmor.js +│ │ ├── TreantSprout.js +│ │ ├── SapTrap.js +│ │ └── BirdFrenzy.js ├── systems/ -│ ├── AspectDropSystem.js # NEW -│ └── AspectManager.js # NEW +│ ├── AbilityDropSystem.js +│ └── AbilityManager.js ├── ui/ -│ └── AspectUI.js # NEW +│ └── AbilityUI.js └── ... ``` ## Testing Checklist -- [ ] Aspects drop from harvested neutrals +- [ ] Abilities drop from harvested plants - [ ] Drop rate scales with fill level -- [ ] Aspects can be equipped/unequipped +- [ ] Abilities can be equipped/unequipped - [ ] Passive effects apply correctly - [ ] Active abilities trigger and go on cooldown -- [ ] UI shows equipped aspects -- [ ] UI shows cooldown state -- [ ] Alignment scaling works correctly +- [ ] UI shows equipped abilities ## Dependencies -- Phase 1: Multiple Neutral Types -- Phase 2: Enemy Type System -- Phase 3: Alignment Effects +- Phase 1-3 ## Next Phase -[Phase 5: Aspect Scaling](./05-phase-aspect-scaling.md) - Implement detailed alignment scaling and visual feedback for powered-up aspects. +[Phase 5: Ability Scaling](./05-phase-aspect-scaling.md) diff --git a/plans/world-build-system/05-phase-aspect-scaling.md b/plans/world-build-system/05-phase-aspect-scaling.md index 15d5a8b..a1445d4 100644 --- a/plans/world-build-system/05-phase-aspect-scaling.md +++ b/plans/world-build-system/05-phase-aspect-scaling.md @@ -1,34 +1,33 @@ -# Phase 5: Aspect Scaling +# Phase 5: Ability Scaling ## Overview -Implement detailed alignment scaling for Aspects, including visual feedback when aspects are powered up, threshold bonuses, and the pure build bonus system. +Implement detailed garden focus scaling for abilities, including visual feedback when abilities are powered up and the pure garden bonus system. ## Goals -- [ ] Implement tiered scaling based on alignment thresholds -- [ ] Add visual feedback for powered-up aspects -- [ ] Create pure build bonus (80%+ alignment) -- [ ] Add penalty system for off-alignment aspects -- [ ] Implement aspect power indicators in UI -- [ ] Add floating damage numbers showing bonus damage +- [ ] Implement tiered scaling based on focus thresholds +- [ ] Add visual feedback for powered-up abilities +- [ ] Create pure garden bonus (80%+ focus) +- [ ] Add penalty system for off-type abilities +- [ ] Implement power indicators in UI ## Scaling Tiers -| Alignment % | Matching Aspect Bonus | Off-Type Penalty | -|-------------|----------------------|------------------| +| Focus % | Matching Ability Bonus | Off-Type Penalty | +|---------|------------------------|------------------| | 0-33% | 1.0x (base) | 1.0x | | 34-59% | 1.25x | 0.9x | | 60-79% | 1.5x | 0.75x | -| 80-100% | 2.0x (pure build) | 0.5x | +| 80-100% | 2.0x (pure garden) | 0.5x | -## Enhanced Scaling System +## Scaling System ```javascript -// js/systems/AspectScalingSystem.js -export class AspectScalingSystem { - constructor(alignmentSystem) { - this.alignmentSystem = alignmentSystem; +// js/systems/AbilityScalingSystem.js +export class AbilityScalingSystem { + constructor(gardenFocusSystem) { + this.gardenFocusSystem = gardenFocusSystem; this.scalingTiers = [ { threshold: 80, matchBonus: 2.0, offPenalty: 0.5 }, @@ -38,35 +37,20 @@ export class AspectScalingSystem { ]; } - getScaleForAspect(aspect) { - const alignment = this.alignmentSystem.getAlignment(); - const aspectType = aspect.alignmentType; - const matchingPercent = alignment[aspectType] || 33; + getScaleForAbility(ability) { + const focus = this.gardenFocusSystem.getFocus(); + const abilityType = ability.focusType; + const matchingPercent = focus[abilityType] || 33; - // Find applicable tier const tier = this.scalingTiers.find(t => matchingPercent >= t.threshold); return { multiplier: tier.matchBonus, tier: this.getTierName(tier.threshold), - isPureBuild: tier.threshold >= 80 + isPureGarden: tier.threshold >= 80 }; } - getOffTypeScale(aspectType) { - const alignment = this.alignmentSystem.getAlignment(); - - // Check if any alignment is dominant enough to penalize off-types - for (const [type, percent] of Object.entries(alignment)) { - if (type !== aspectType && percent >= 60) { - const tier = this.scalingTiers.find(t => percent >= t.threshold); - return tier.offPenalty; - } - } - - return 1.0; // No penalty - } - getTierName(threshold) { switch(threshold) { case 80: return 'pure'; @@ -78,313 +62,61 @@ export class AspectScalingSystem { } ``` -## Visual Feedback System +## Visual Feedback -### Aspect Glow Effects +### Ability Glow Effects -```javascript -// js/ui/AspectVisualEffects.js -export class AspectVisualEffects { - constructor() { - this.glowColors = { - base: 'rgba(255, 255, 255, 0)', - minor: 'rgba(255, 255, 100, 0.3)', - major: 'rgba(255, 200, 50, 0.5)', - pure: 'rgba(255, 150, 0, 0.8)' - }; - - this.pulsePhase = 0; - } - - update(deltaTime) { - this.pulsePhase += deltaTime * 3; - } - - drawAspectGlow(ctx, x, y, size, tier) { - const baseColor = this.glowColors[tier]; - if (tier === 'base') return; - - // Pulsing glow - const pulse = 0.7 + 0.3 * Math.sin(this.pulsePhase); - - ctx.save(); - ctx.shadowColor = baseColor; - ctx.shadowBlur = 15 * pulse; - ctx.fillStyle = baseColor; - ctx.fillRect(x, y, size, size); - ctx.restore(); - - // Pure build special effect - if (tier === 'pure') { - this.drawPureBuildEffect(ctx, x, y, size); - } - } - - drawPureBuildEffect(ctx, x, y, size) { - // Particle ring around aspect - const centerX = x + size / 2; - const centerY = y + size / 2; - const radius = size * 0.8; - - for (let i = 0; i < 8; i++) { - const angle = (i / 8) * Math.PI * 2 + this.pulsePhase * 0.5; - const px = centerX + Math.cos(angle) * radius; - const py = centerY + Math.sin(angle) * radius; - - ctx.beginPath(); - ctx.arc(px, py, 3, 0, Math.PI * 2); - ctx.fillStyle = 'rgba(255, 200, 100, 0.8)'; - ctx.fill(); - } - } -} -``` +| Tier | Visual Effect | +|------|---------------| +| Base | No glow | +| Minor | Subtle colored outline | +| Major | Pulsing glow | +| Pure | Bright glow + particle effects | -### Floating Damage Numbers +### Pure Garden Notification -```javascript -// js/ui/FloatingNumbers.js -export class FloatingNumbers { - constructor() { - this.numbers = []; - } - - addNumber(x, y, value, type = 'normal') { - const colors = { - normal: '#ffffff', - bonus: '#ffff00', - critical: '#ff4444', - resist: '#888888', - weak: '#00ff00' - }; - - this.numbers.push({ - x, y, - value: Math.round(value * 10) / 10, - color: colors[type], - scale: type === 'critical' ? 1.5 : 1.0, - lifetime: 1.0, - vy: -50 - }); - } - - update(deltaTime) { - for (let i = this.numbers.length - 1; i >= 0; i--) { - const num = this.numbers[i]; - num.y += num.vy * deltaTime; - num.lifetime -= deltaTime; - - if (num.lifetime <= 0) { - this.numbers.splice(i, 1); - } - } - } - - draw(ctx, cameraX, cameraY) { - for (const num of this.numbers) { - const screenX = num.x - cameraX; - const screenY = num.y - cameraY; - const alpha = Math.min(1, num.lifetime * 2); - - ctx.save(); - ctx.globalAlpha = alpha; - ctx.font = `bold ${16 * num.scale}px Arial`; - ctx.fillStyle = num.color; - ctx.textAlign = 'center'; - ctx.fillText(num.value.toString(), screenX, screenY); - ctx.restore(); - } - } -} -``` +When reaching 80% focus: +- Large notification: "PURE FLOWER GARDEN!" (or Vegetable/Tree) +- Screen flash in garden color +- Ability icons gain special border ## Power Indicator UI ```javascript -// js/ui/PowerIndicator.js -export class PowerIndicator { - constructor() { - this.indicators = { - arcane: { current: 1.0, target: 1.0 }, - primal: { current: 1.0, target: 1.0 }, - forged: { current: 1.0, target: 1.0 } - }; - } - - update(deltaTime, scalingSystem) { - // Smoothly animate toward target values - for (const type of ['arcane', 'primal', 'forged']) { - const scale = scalingSystem.getScaleForAspect({ alignmentType: type }); - this.indicators[type].target = scale.multiplier; - - const diff = this.indicators[type].target - this.indicators[type].current; - this.indicators[type].current += diff * deltaTime * 3; - } - } - - draw(ctx, x, y) { - const barWidth = 100; - const barHeight = 8; - const spacing = 12; - - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - - let offsetY = 0; - for (const [type, indicator] of Object.entries(this.indicators)) { - // Background - ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; - ctx.fillRect(x, y + offsetY, barWidth, barHeight); - - // Fill based on multiplier (1.0 = 50%, 2.0 = 100%) - const fillPercent = (indicator.current - 0.5) / 1.5; - ctx.fillStyle = colors[type]; - ctx.fillRect(x, y + offsetY, barWidth * fillPercent, barHeight); - - // Label - ctx.fillStyle = '#ffffff'; - ctx.font = '10px Arial'; - ctx.fillText(`${type[0].toUpperCase()}: ${indicator.current.toFixed(1)}x`, - x + barWidth + 5, y + offsetY + 7); - - offsetY += spacing; - } - } -} -``` - -## Enhanced Aspect Base Class - -```javascript -// Updates to Aspect.js -getScaledEffect(scalingSystem) { - const scale = scalingSystem.getScaleForAspect(this); - const offTypeScale = scalingSystem.getOffTypeScale(this.alignmentType); - - const finalMultiplier = scale.multiplier * offTypeScale; - - const scaledEffect = {}; - for (const [key, value] of Object.entries(this.baseEffect)) { - scaledEffect[key] = value * finalMultiplier; - } - - return { - ...scaledEffect, - tier: scale.tier, - isPureBuild: scale.isPureBuild, - multiplier: finalMultiplier - }; -} - -// Visual indicator of power level -getPowerLevel(scalingSystem) { - const scale = scalingSystem.getScaleForAspect(this); - return { - tier: scale.tier, - multiplier: scale.multiplier, - color: this.getTierColor(scale.tier) - }; -} - -getTierColor(tier) { - const colors = { - base: '#888888', - minor: '#aaaaaa', - major: '#ffcc00', - pure: '#ff8800' - }; - return colors[tier]; -} -``` - -## Pure Build Notification - -```javascript -// js/ui/PureBuildNotification.js -export class PureBuildNotification { - constructor() { - this.active = false; - this.alignmentType = null; - this.animationPhase = 0; - this.displayTime = 3; - } - - trigger(alignmentType) { - this.active = true; - this.alignmentType = alignmentType; - this.animationPhase = 0; - this.displayTime = 3; - } - - update(deltaTime) { - if (!this.active) return; - - this.animationPhase += deltaTime; - this.displayTime -= deltaTime; - - if (this.displayTime <= 0) { - this.active = false; - } - } - - draw(ctx, canvasWidth, canvasHeight) { - if (!this.active) return; - - const centerX = canvasWidth / 2; - const centerY = canvasHeight / 3; - - // Fade in/out - const alpha = this.displayTime > 2.5 ? - (3 - this.displayTime) * 2 : - Math.min(1, this.displayTime / 0.5); - - ctx.save(); - ctx.globalAlpha = alpha; - - // Glow effect - ctx.shadowColor = this.getTypeColor(); - ctx.shadowBlur = 20 + 10 * Math.sin(this.animationPhase * 5); - - // Text - ctx.font = 'bold 36px Arial'; - ctx.fillStyle = this.getTypeColor(); - ctx.textAlign = 'center'; - ctx.fillText('PURE BUILD ACHIEVED', centerX, centerY); - - ctx.font = '24px Arial'; - ctx.fillText(`${this.alignmentType.toUpperCase()} MASTERY`, centerX, centerY + 40); - - ctx.restore(); - } - - getTypeColor() { - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - return colors[this.alignmentType] || '#ffffff'; - } +// Shows current ability power levels +drawPowerIndicator(ctx, x, y, focus) { + const barWidth = 100; + const barHeight = 8; + + // Flower power bar (pink) + ctx.fillStyle = '#ff88cc'; + ctx.fillRect(x, y, barWidth * (focus.flowers / 100), barHeight); + ctx.fillText(`🌸 ${this.getMultiplier('flowers')}x`, x + barWidth + 5, y + 7); + + // Vegetable power bar (green) + ctx.fillStyle = '#44aa44'; + ctx.fillRect(x, y + 12, barWidth * (focus.vegetables / 100), barHeight); + ctx.fillText(`🥕 ${this.getMultiplier('vegetables')}x`, x + barWidth + 5, y + 19); + + // Tree power bar (brown) + ctx.fillStyle = '#8B4513'; + ctx.fillRect(x, y + 24, barWidth * (focus.trees / 100), barHeight); + ctx.fillText(`🌳 ${this.getMultiplier('trees')}x`, x + barWidth + 5, y + 31); } ``` ## Testing Checklist -- [ ] Aspects scale correctly at each threshold -- [ ] Off-type penalties apply when alignment is high +- [ ] Abilities scale correctly at each threshold +- [ ] Off-type penalties apply when focus is high - [ ] Visual glow effects show power level -- [ ] Pure build notification triggers at 80% -- [ ] Floating damage numbers show bonus damage +- [ ] Pure garden notification triggers at 80% - [ ] Power indicator UI updates smoothly -- [ ] Aspect UI shows tier coloring ## Dependencies -- Phase 1-4 (all previous phases) +- Phase 1-4 ## Next Phase -[Phase 6: Ecosystem Interactions](./06-phase-ecosystem.md) - Implement neutral proximity effects and hybrid terrain features. +[Phase 6: Companion Planting](./06-phase-ecosystem.md) diff --git a/plans/world-build-system/06-phase-ecosystem.md b/plans/world-build-system/06-phase-ecosystem.md index e7042d2..101f7a2 100644 --- a/plans/world-build-system/06-phase-ecosystem.md +++ b/plans/world-build-system/06-phase-ecosystem.md @@ -1,234 +1,173 @@ -# Phase 6: Ecosystem Interactions +# Phase 6: Companion Planting (Ecosystem) ## Overview -Implement the ecosystem where neutrals interact with each other based on proximity, creating hybrid terrain features and chain reactions that add emergent gameplay. +Implement the companion planting system where plants near each other create beneficial effects, hybrid terrain features, and special plant varieties. ## Goals -- [ ] Implement neutral proximity detection system -- [ ] Create hybrid terrain features (Enchanted Grove, Power Conduit, Overgrown Ruins) -- [ ] Add chain reaction effects when harvesting -- [ ] Create hybrid neutral types -- [ ] Implement ecosystem visualization (connection lines, auras) +- [ ] Implement plant proximity detection +- [ ] Create companion planting effects +- [ ] Add hybrid garden features (Pollinator Garden, Orchard, Food Forest) +- [ ] Create hybrid plant types +- [ ] Implement visual connections between companion plants -## Proximity Detection System +## Companion Planting Concept + +In real gardening, certain plants grow better together. We'll use this concept: + +| Combination | Name | Effect | +|-------------|------|--------| +| Flower + Vegetable | **Pollinator Garden** | Bees boost both, healing + damage | +| Flower + Tree | **Orchard** | Fruit trees, beauty + structure | +| Vegetable + Tree | **Food Forest** | Sustainable harvest, defense + sustain | + +## Proximity Detection ```javascript -// js/systems/EcosystemSystem.js -export class EcosystemSystem { +// js/systems/CompanionPlantingSystem.js +export class CompanionPlantingSystem { constructor(gameState) { this.gameState = gameState; - this.proximityRadius = 150; - this.hybridFeatures = []; + this.companionRadius = 150; this.connections = []; + this.hybridFeatures = []; } update(deltaTime) { this.updateConnections(); this.checkForHybridSpawns(); - this.updateHybridFeatures(deltaTime); } updateConnections() { this.connections = []; - const neutrals = this.gameState.neutralEntities; + const plants = this.gameState.plants; - for (let i = 0; i < neutrals.length; i++) { - for (let j = i + 1; j < neutrals.length; j++) { - const n1 = neutrals[i]; - const n2 = neutrals[j]; + for (let i = 0; i < plants.length; i++) { + for (let j = i + 1; j < plants.length; j++) { + const p1 = plants[i]; + const p2 = plants[j]; - if (n1.alignmentType === n2.alignmentType) continue; + if (p1.focusType === p2.focusType) continue; - const dist = this.getDistance(n1, n2); - if (dist <= this.proximityRadius) { + const dist = this.getDistance(p1, p2); + if (dist <= this.companionRadius) { this.connections.push({ - from: n1, - to: n2, - types: [n1.alignmentType, n2.alignmentType].sort(), - strength: 1 - (dist / this.proximityRadius) + from: p1, + to: p2, + types: [p1.focusType, p2.focusType].sort(), + strength: 1 - (dist / this.companionRadius) }); } } } } - - getDistance(a, b) { - const dx = a.x - b.x; - const dy = a.y - b.y; - return Math.sqrt(dx * dx + dy * dy); - } } ``` -## Hybrid Terrain Features - -### Enchanted Grove (Arcane + Primal) -- **Appearance**: Glowing plants, bioluminescent flowers -- **Effect**: Heals player 2 HP/sec, regenerates mana -- **Spawns**: Living Crystal hybrid neutrals - -### Power Conduit (Arcane + Forged) -- **Appearance**: Electrified metal structures, arcing lightning -- **Effect**: Damages enemies 3 DPS, powers nearby constructs -- **Spawns**: Charged Ore hybrid neutrals - -### Overgrown Ruins (Primal + Forged) -- **Appearance**: Vines wrapping metal, organic-mechanical fusion -- **Effect**: Creates regenerating walls, slows enemies 40% -- **Spawns**: Ironwood hybrid neutrals - -## Chain Reaction Effects - -When harvesting a neutral near another type: - -```javascript -// In EcosystemSystem.js -onNeutralHarvested(neutral, position) { - const nearbyNeutrals = this.getNeutralsInRange(position, this.proximityRadius); - - for (const nearby of nearbyNeutrals) { - if (nearby.alignmentType === neutral.alignmentType) continue; - - // Apply chain effect based on combination - this.applyChainEffect(neutral.alignmentType, nearby); - } -} - -applyChainEffect(harvestedType, targetNeutral) { - const combo = [harvestedType, targetNeutral.alignmentType].sort().join('_'); - - switch(combo) { - case 'arcane_primal': - // Crystal energizes Seed - faster growth - targetNeutral.growthMultiplier = 1.5; - targetNeutral.bonusYield = 1; - break; - - case 'arcane_forged': - // Crystal charges Ore - becomes magnetic - targetNeutral.charged = true; - targetNeutral.pullRadius = 100; - break; - - case 'forged_primal': - // Ore reinforces Seed - becomes armored - targetNeutral.armor = 5; - targetNeutral.bonusYield = 2; - break; - } -} -``` +## Hybrid Garden Features -## Hybrid Neutral Types +### Pollinator Garden (Flowers + Vegetables) +- **Appearance**: Buzzing bees, colorful mixed plantings +- **Effect**: +25% to both flower and vegetable abilities, healing aura +- **Spawns**: Bee swarms that attack pests -### Living Crystal (Arcane + Primal) ```javascript -// js/entities/neutrals/hybrid/LivingCrystal.js -export class LivingCrystal extends NeutralEntity { +// js/entities/terrain/hybrid/PollinatorGarden.js +export class PollinatorGarden extends GardenFeature { constructor(x, y) { - super(x, y, 40); - this.type = 'living_crystal'; - this.alignmentTypes = ['arcane', 'primal']; - this.color = '#00ffaa'; - - this.harvestReward = { - arcane: 3, - primal: 3 - }; + super(x, y, 100); + this.hybridType = 'pollinator_garden'; + this.focusTypes = ['flowers', 'vegetables']; - // Unique: slowly regenerates fill level - this.regenRate = 0.5; + this.healPerSecond = 1; + this.damageBonus = 0.25; + this.bees = []; } - update(deltaTime) { - super.update(deltaTime); + update(deltaTime, pests) { + // Bees attack nearby pests + for (const bee of this.bees) { + bee.update(deltaTime, pests); + } - // Regenerate if not full - if (this.fillLevel < this.maxFill && this.fillLevel > 0) { - this.fillLevel = Math.min( - this.maxFill, - this.fillLevel + this.regenRate * deltaTime - ); + // Spawn new bees periodically + if (Math.random() < deltaTime * 0.5) { + this.spawnBee(); } } } ``` -### Charged Ore (Arcane + Forged) +### Orchard (Flowers + Trees) +- **Appearance**: Flowering fruit trees, blossoms falling +- **Effect**: Fruit drops heal player, beautiful barrier +- **Spawns**: Fruit that can be collected for health + ```javascript -// js/entities/neutrals/hybrid/ChargedOre.js -export class ChargedOre extends NeutralEntity { +// js/entities/terrain/hybrid/Orchard.js +export class Orchard extends GardenFeature { constructor(x, y) { - super(x, y, 45); - this.type = 'charged_ore'; - this.alignmentTypes = ['arcane', 'forged']; - this.color = '#8888ff'; - - this.harvestReward = { - arcane: 3, - forged: 3 - }; + super(x, y, 120); + this.hybridType = 'orchard'; + this.focusTypes = ['flowers', 'trees']; - // Unique: damages nearby enemies - this.damageRadius = 80; - this.damagePerSecond = 1; - } - - update(deltaTime, enemies) { - super.update(deltaTime); - - // Damage nearby enemies - for (const enemy of enemies) { - const dist = this.getDistanceTo(enemy); - if (dist <= this.damageRadius) { - enemy.takeDamage(this.damagePerSecond * deltaTime, 'arcane'); - } - } + this.fruitDropInterval = 5; + this.fruitHealAmount = 5; + this.blossoms = []; } } ``` -### Ironwood (Primal + Forged) +### Food Forest (Vegetables + Trees) +- **Appearance**: Layered garden with trees, shrubs, ground cover +- **Effect**: Sustainable harvest, regenerating walls +- **Spawns**: Living walls that regrow when damaged + ```javascript -// js/entities/neutrals/hybrid/Ironwood.js -export class Ironwood extends NeutralEntity { +// js/entities/terrain/hybrid/FoodForest.js +export class FoodForest extends GardenFeature { constructor(x, y) { - super(x, y, 50); - this.type = 'ironwood'; - this.alignmentTypes = ['primal', 'forged']; - this.color = '#668844'; - - this.harvestReward = { - primal: 3, - forged: 3 - }; + super(x, y, 100); + this.hybridType = 'food_forest'; + this.focusTypes = ['vegetables', 'trees']; - // Unique: extremely heavy, creates large safe zone - this.mass = 500; - this.blockRadius = 100; + this.wallHealth = 50; + this.wallRegen = 2; // HP per second + this.harvestBonus = 1.5; } } ``` -## Connection Visualization +## Hybrid Plant Types + +### Sunflower (Flower + Vegetable hybrid) +- **Visual**: Tall yellow sunflower +- **Harvest**: Gives both flower and vegetable focus +- **Special**: Seeds can be planted for more sunflowers + +### Fruit Tree (Flower + Tree hybrid) +- **Visual**: Tree with blossoms and fruit +- **Harvest**: Gives both flower and tree focus +- **Special**: Drops fruit periodically + +### Berry Bush (Vegetable + Tree hybrid) +- **Visual**: Bushy shrub with berries +- **Harvest**: Gives both vegetable and tree focus +- **Special**: Berries heal player on contact + +## Visual Connections ```javascript -// js/ui/EcosystemVisuals.js -export class EcosystemVisuals { - constructor(ecosystemSystem) { - this.ecosystemSystem = ecosystemSystem; +// js/ui/CompanionVisuals.js +export class CompanionVisuals { + constructor(companionSystem) { + this.companionSystem = companionSystem; this.pulsePhase = 0; } - update(deltaTime) { - this.pulsePhase += deltaTime * 2; - } - draw(ctx, cameraX, cameraY) { - // Draw connections between nearby neutrals - for (const conn of this.ecosystemSystem.connections) { + for (const conn of this.companionSystem.connections) { this.drawConnection(ctx, conn, cameraX, cameraY); } } @@ -239,43 +178,40 @@ export class EcosystemVisuals { const x2 = connection.to.x - cameraX; const y2 = connection.to.y - cameraY; - // Get color based on types + // Draw dotted line with hearts/bees/leaves const color = this.getConnectionColor(connection.types); - const alpha = connection.strength * 0.5 * (0.7 + 0.3 * Math.sin(this.pulsePhase)); - - // Draw glowing line + ctx.strokeStyle = color; + ctx.setLineDash([5, 10]); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); - ctx.strokeStyle = color.replace('1)', `${alpha})`); - ctx.lineWidth = 3; ctx.stroke(); + ctx.setLineDash([]); - // Draw energy particles along line - this.drawEnergyParticles(ctx, x1, y1, x2, y2, color); + // Draw companion icon at midpoint + const midX = (x1 + x2) / 2; + const midY = (y1 + y2) / 2; + this.drawCompanionIcon(ctx, midX, midY, connection.types); } getConnectionColor(types) { - if (types.includes('arcane') && types.includes('primal')) { - return 'rgba(0, 255, 170, 1)'; - } else if (types.includes('arcane') && types.includes('forged')) { - return 'rgba(136, 136, 255, 1)'; + if (types.includes('flowers') && types.includes('vegetables')) { + return 'rgba(255, 200, 100, 0.6)'; // Golden (bees) + } else if (types.includes('flowers') && types.includes('trees')) { + return 'rgba(255, 180, 200, 0.6)'; // Pink (blossoms) } else { - return 'rgba(102, 136, 68, 1)'; + return 'rgba(100, 150, 100, 0.6)'; // Green (forest) } } - drawEnergyParticles(ctx, x1, y1, x2, y2, color) { - const particleCount = 3; - for (let i = 0; i < particleCount; i++) { - const t = ((this.pulsePhase * 0.5 + i / particleCount) % 1); - const px = x1 + (x2 - x1) * t; - const py = y1 + (y2 - y1) * t; - - ctx.beginPath(); - ctx.arc(px, py, 4, 0, Math.PI * 2); - ctx.fillStyle = color; - ctx.fill(); + drawCompanionIcon(ctx, x, y, types) { + // Draw bee, blossom, or leaf icon + if (types.includes('flowers') && types.includes('vegetables')) { + ctx.fillText('🐝', x - 8, y + 5); + } else if (types.includes('flowers') && types.includes('trees')) { + ctx.fillText('🌸', x - 8, y + 5); + } else { + ctx.fillText('🍃', x - 8, y + 5); } } } @@ -286,37 +222,36 @@ export class EcosystemVisuals { ``` vampire-survivor/js/ ├── entities/ -│ ├── neutrals/ +│ ├── plants/ │ │ ├── hybrid/ -│ │ │ ├── LivingCrystal.js # NEW -│ │ │ ├── ChargedOre.js # NEW -│ │ │ └── Ironwood.js # NEW +│ │ │ ├── Sunflower.js +│ │ │ ├── FruitTree.js +│ │ │ └── BerryBush.js │ ├── terrain/ │ │ ├── hybrid/ -│ │ │ ├── EnchantedGrove.js # NEW -│ │ │ ├── PowerConduit.js # NEW -│ │ │ └── OvergrownRuins.js # NEW +│ │ │ ├── PollinatorGarden.js +│ │ │ ├── Orchard.js +│ │ │ └── FoodForest.js ├── systems/ -│ └── EcosystemSystem.js # NEW +│ └── CompanionPlantingSystem.js ├── ui/ -│ └── EcosystemVisuals.js # NEW +│ └── CompanionVisuals.js └── ... ``` ## Testing Checklist -- [ ] Connections form between nearby different-type neutrals +- [ ] Connections form between nearby different-type plants - [ ] Connection strength scales with distance - [ ] Hybrid terrain spawns at connection midpoints -- [ ] Chain reactions trigger on harvest -- [ ] Hybrid neutrals spawn from hybrid terrain +- [ ] Hybrid plants spawn from hybrid terrain - [ ] Visual connections render correctly -- [ ] Energy particles animate along connections +- [ ] Companion icons animate along connections ## Dependencies -- Phase 1-5 (all previous phases) +- Phase 1-5 ## Next Phase -[Phase 7: Dual Map System](./07-phase-dual-map.md) - Implement the Creation Map for peaceful building and planning. +[Phase 7: Dual Plot System](./07-phase-dual-map.md) diff --git a/plans/world-build-system/07-phase-dual-map.md b/plans/world-build-system/07-phase-dual-map.md index aadb806..20b1bfa 100644 --- a/plans/world-build-system/07-phase-dual-map.md +++ b/plans/world-build-system/07-phase-dual-map.md @@ -1,119 +1,70 @@ -# Phase 7: Dual Map System +# Phase 7: Dual Plot System ## Overview -Implement the dual map system with a PvE Map for combat and a Creation Map for peaceful planning, planting, and aspect management. +Implement the dual plot system with a Garden Plot for combat/defense and a Planning Mode for peaceful planting and ability management. ## Goals -- [ ] Create MapManager for switching between maps -- [ ] Implement Creation Map state (paused enemies, builder UI) +- [ ] Create PlotManager for switching between modes +- [ ] Implement Planning Mode (paused pests, planting UI) - [ ] Add seed planting system -- [ ] Create Aspect management UI for Creation Map -- [ ] Implement map transition effects -- [ ] Add enemy distribution preview -- [ ] Sync planted seeds to PvE Map as growing neutrals +- [ ] Create ability management UI for Planning Mode +- [ ] Implement mode transition effects +- [ ] Add pest distribution preview -## Map Manager +## Plot Manager ```javascript -// js/systems/MapManager.js -export class MapManager { +// js/systems/PlotManager.js +export class PlotManager { constructor(gameState) { this.gameState = gameState; - this.currentMap = 'pve'; // 'pve' or 'creation' + this.currentMode = 'garden'; // 'garden' or 'planning' this.transitionProgress = 0; this.isTransitioning = false; - this.transitionDuration = 0.3; } - switchMap() { + switchMode() { if (this.isTransitioning) return; this.isTransitioning = true; this.transitionProgress = 0; - - // Target map - this.targetMap = this.currentMap === 'pve' ? 'creation' : 'pve'; - } - - update(deltaTime) { - if (!this.isTransitioning) return; - - this.transitionProgress += deltaTime / this.transitionDuration; - - if (this.transitionProgress >= 1) { - this.completeTransition(); - } + this.targetMode = this.currentMode === 'garden' ? 'planning' : 'garden'; } completeTransition() { - this.currentMap = this.targetMap; + this.currentMode = this.targetMode; this.isTransitioning = false; - this.transitionProgress = 0; - if (this.currentMap === 'creation') { - this.enterCreationMode(); + if (this.currentMode === 'planning') { + this.enterPlanningMode(); } else { - this.enterPvEMode(); + this.enterGardenMode(); } } - enterCreationMode() { - // Pause enemy AI - this.gameState.enemiesPaused = true; + enterPlanningMode() { + // Pause pest AI + this.gameState.pestsPaused = true; - // Show builder UI - this.gameState.showBuilderUI = true; + // Show planting UI + this.gameState.showPlanningUI = true; - // Enable placement mode + // Enable seed placement this.gameState.placementMode = true; } - enterPvEMode() { - // Resume enemy AI - this.gameState.enemiesPaused = false; - - // Hide builder UI - this.gameState.showBuilderUI = false; + enterGardenMode() { + // Resume pest AI + this.gameState.pestsPaused = false; - // Disable placement mode - this.gameState.placementMode = false; + // Hide planting UI + this.gameState.showPlanningUI = false; - // Sync any planted seeds + // Sync planted seeds this.syncPlantedSeeds(); } - - syncPlantedSeeds() { - for (const seed of this.gameState.plantedSeeds) { - if (!seed.synced) { - // Create growing neutral at seed position - const neutral = this.createNeutralFromSeed(seed); - this.gameState.neutralEntities.push(neutral); - seed.synced = true; - } - } - } - - createNeutralFromSeed(seed) { - // Create neutral based on seed type - switch(seed.type) { - case 'arcane': - return new CrystalNode(seed.x, seed.y); - case 'primal': - return new SeedPod(seed.x, seed.y); - case 'forged': - return new OreVein(seed.x, seed.y); - } - } - - isPvEMap() { - return this.currentMap === 'pve'; - } - - isCreationMap() { - return this.currentMap === 'creation'; - } } ``` @@ -148,16 +99,11 @@ export class PlantingSystem { } isValidPlacement(x, y) { - // Check minimum distance from other neutrals/seeds - const minDistance = 100; - - for (const neutral of this.gameState.neutralEntities) { - const dist = Math.sqrt((neutral.x - x) ** 2 + (neutral.y - y) ** 2); - if (dist < minDistance) return false; - } + const minDistance = 80; - for (const seed of this.plantedSeeds) { - const dist = Math.sqrt((seed.x - x) ** 2 + (seed.y - y) ** 2); + // Check distance from existing plants + for (const plant of this.gameState.plants) { + const dist = Math.sqrt((plant.x - x) ** 2 + (plant.y - y) ** 2); if (dist < minDistance) return false; } @@ -168,297 +114,141 @@ export class PlantingSystem { if (!this.selectedSeedType) return false; if (!this.isValidPlacement(x, y)) return false; - // Check if player has seeds - const seedCount = this.gameState.resources.seeds[this.selectedSeedType] || 0; + // Check seed inventory + const seedCount = this.gameState.seeds[this.selectedSeedType] || 0; if (seedCount <= 0) return false; // Consume seed - this.gameState.resources.seeds[this.selectedSeedType]--; + this.gameState.seeds[this.selectedSeedType]--; // Create planted seed const seed = { x, y, type: this.selectedSeedType, plantedAt: Date.now(), - growthTime: 60, // seconds to mature - synced: false + growthTime: 45 // seconds to mature }; this.plantedSeeds.push(seed); - this.gameState.plantedSeeds.push(seed); - return true; } - - drawPreview(ctx, cameraX, cameraY) { - if (!this.placementPreview) return; - - const screenX = this.placementPreview.x - cameraX; - const screenY = this.placementPreview.y - cameraY; - - // Draw preview circle - ctx.beginPath(); - ctx.arc(screenX, screenY, 30, 0, Math.PI * 2); - ctx.fillStyle = this.placementPreview.valid ? - 'rgba(0, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)'; - ctx.fill(); - ctx.strokeStyle = this.placementPreview.valid ? '#00ff00' : '#ff0000'; - ctx.stroke(); - - // Draw type indicator - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - ctx.fillStyle = colors[this.placementPreview.type]; - ctx.beginPath(); - ctx.arc(screenX, screenY, 15, 0, Math.PI * 2); - ctx.fill(); - } } ``` -## Creation Map UI +## Planning Mode UI ```javascript -// js/ui/CreationMapUI.js -export class CreationMapUI { - constructor(gameState, aspectManager, plantingSystem, alignmentSystem) { +// js/ui/PlanningModeUI.js +export class PlanningModeUI { + constructor(gameState, abilityManager, plantingSystem) { this.gameState = gameState; - this.aspectManager = aspectManager; + this.abilityManager = abilityManager; this.plantingSystem = plantingSystem; - this.alignmentSystem = alignmentSystem; - - this.panels = { - seeds: { x: 10, y: 100, width: 200, height: 150 }, - aspects: { x: 10, y: 270, width: 200, height: 200 }, - alignment: { x: 10, y: 490, width: 200, height: 100 }, - enemies: { x: 10, y: 610, width: 200, height: 100 } - }; } draw(ctx, canvasWidth, canvasHeight) { - // Semi-transparent overlay - ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; + // Peaceful overlay + ctx.fillStyle = 'rgba(200, 230, 200, 0.2)'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); // Draw panels this.drawSeedPanel(ctx); - this.drawAspectPanel(ctx); - this.drawAlignmentPanel(ctx); - this.drawEnemyPreviewPanel(ctx); + this.drawAbilityPanel(ctx); + this.drawFocusPanel(ctx); + this.drawPestPreviewPanel(ctx); - // Draw mode indicator + // Mode indicator this.drawModeIndicator(ctx, canvasWidth); } drawSeedPanel(ctx) { - const panel = this.panels.seeds; - this.drawPanelBackground(ctx, panel, 'Seeds'); - - const seeds = this.gameState.resources.seeds; - const types = ['arcane', 'primal', 'forged']; - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - - let y = panel.y + 40; - for (const type of types) { - const count = seeds[type] || 0; - const selected = this.plantingSystem.selectedSeedType === type; - - // Seed button - ctx.fillStyle = selected ? colors[type] : 'rgba(50, 50, 50, 0.8)'; - ctx.fillRect(panel.x + 10, y, 30, 30); - ctx.strokeStyle = colors[type]; - ctx.strokeRect(panel.x + 10, y, 30, 30); - - // Label and count - ctx.fillStyle = '#ffffff'; - ctx.font = '14px Arial'; - ctx.fillText(`${type}: ${count}`, panel.x + 50, y + 20); - - y += 40; - } - } - - drawAspectPanel(ctx) { - const panel = this.panels.aspects; - this.drawPanelBackground(ctx, panel, 'Aspects'); - - // Equipped aspects - ctx.fillStyle = '#aaaaaa'; - ctx.font = '12px Arial'; - ctx.fillText('Equipped:', panel.x + 10, panel.y + 40); - - let y = panel.y + 55; - for (const aspect of this.aspectManager.equipped) { - ctx.fillStyle = this.getAspectColor(aspect.alignmentType); - ctx.fillRect(panel.x + 10, y, 20, 20); - ctx.fillStyle = '#ffffff'; - ctx.fillText(aspect.name, panel.x + 35, y + 15); - y += 25; - } - - // Inventory - ctx.fillStyle = '#aaaaaa'; - ctx.fillText('Inventory:', panel.x + 10, y + 15); - y += 30; - - for (const aspect of this.aspectManager.inventory.slice(0, 4)) { - ctx.fillStyle = this.getAspectColor(aspect.alignmentType); - ctx.fillRect(panel.x + 10, y, 20, 20); - ctx.fillStyle = '#ffffff'; - ctx.fillText(aspect.name, panel.x + 35, y + 15); - y += 25; - } - } - - drawAlignmentPanel(ctx) { - const panel = this.panels.alignment; - this.drawPanelBackground(ctx, panel, 'Alignment'); - - const alignment = this.alignmentSystem.getAlignment(); - const barWidth = panel.width - 20; - const barHeight = 20; - let y = panel.y + 40; - - // Stacked bar - ctx.fillStyle = '#00aaff'; - ctx.fillRect(panel.x + 10, y, barWidth * (alignment.arcane / 100), barHeight); + const x = 10, y = 100; - ctx.fillStyle = '#44aa44'; - ctx.fillRect(panel.x + 10 + barWidth * (alignment.arcane / 100), y, - barWidth * (alignment.primal / 100), barHeight); + ctx.fillStyle = 'rgba(20, 40, 20, 0.9)'; + ctx.fillRect(x, y, 180, 140); - ctx.fillStyle = '#ff8844'; - ctx.fillRect(panel.x + 10 + barWidth * ((alignment.arcane + alignment.primal) / 100), y, - barWidth * (alignment.forged / 100), barHeight); - - // Labels - y += 30; ctx.fillStyle = '#ffffff'; - ctx.font = '12px Arial'; - ctx.fillText(`Arcane: ${alignment.arcane.toFixed(0)}%`, panel.x + 10, y); - ctx.fillText(`Primal: ${alignment.primal.toFixed(0)}%`, panel.x + 10, y + 15); - ctx.fillText(`Forged: ${alignment.forged.toFixed(0)}%`, panel.x + 10, y + 30); - } - - drawEnemyPreviewPanel(ctx) { - const panel = this.panels.enemies; - this.drawPanelBackground(ctx, panel, 'Enemy Distribution'); - - const alignment = this.alignmentSystem.getAlignment(); + ctx.font = 'bold 14px Arial'; + ctx.fillText('🌱 Seeds', x + 10, y + 20); - ctx.fillStyle = '#ffffff'; - ctx.font = '12px Arial'; + // Seed buttons + const seeds = this.gameState.seeds; + let buttonY = y + 40; - let y = panel.y + 40; - ctx.fillStyle = '#6644ff'; - ctx.fillText(`Arcane Enemies: ${alignment.arcane.toFixed(0)}%`, panel.x + 10, y); + // Flower seeds + this.drawSeedButton(ctx, x + 10, buttonY, 'flowers', seeds.flowers || 0, '🌸'); + buttonY += 35; - ctx.fillStyle = '#88aa44'; - ctx.fillText(`Primal Enemies: ${alignment.primal.toFixed(0)}%`, panel.x + 10, y + 20); + // Vegetable seeds + this.drawSeedButton(ctx, x + 10, buttonY, 'vegetables', seeds.vegetables || 0, '🥕'); + buttonY += 35; - ctx.fillStyle = '#aa6644'; - ctx.fillText(`Forged Enemies: ${alignment.forged.toFixed(0)}%`, panel.x + 10, y + 40); + // Tree seeds + this.drawSeedButton(ctx, x + 10, buttonY, 'trees', seeds.trees || 0, '🌳'); } - drawPanelBackground(ctx, panel, title) { - ctx.fillStyle = 'rgba(20, 20, 30, 0.9)'; - ctx.fillRect(panel.x, panel.y, panel.width, panel.height); - ctx.strokeStyle = '#444'; - ctx.strokeRect(panel.x, panel.y, panel.width, panel.height); + drawSeedButton(ctx, x, y, type, count, emoji) { + const selected = this.plantingSystem.selectedSeedType === type; + + ctx.fillStyle = selected ? 'rgba(100, 150, 100, 0.8)' : 'rgba(50, 70, 50, 0.8)'; + ctx.fillRect(x, y, 160, 30); ctx.fillStyle = '#ffffff'; - ctx.font = 'bold 14px Arial'; - ctx.fillText(title, panel.x + 10, panel.y + 20); + ctx.font = '14px Arial'; + ctx.fillText(`${emoji} ${type}: ${count}`, x + 10, y + 20); } drawModeIndicator(ctx, canvasWidth) { - ctx.fillStyle = 'rgba(0, 100, 50, 0.8)'; + ctx.fillStyle = 'rgba(50, 100, 50, 0.9)'; ctx.fillRect(canvasWidth / 2 - 100, 10, 200, 40); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 18px Arial'; ctx.textAlign = 'center'; - ctx.fillText('CREATION MODE', canvasWidth / 2, 35); + ctx.fillText('🌿 PLANNING MODE', canvasWidth / 2, 35); ctx.textAlign = 'left'; ctx.font = '12px Arial'; - ctx.fillText('Press TAB to return to combat', canvasWidth / 2 - 80, 55); - } - - getAspectColor(type) { - const colors = { - arcane: '#00aaff', - primal: '#44aa44', - forged: '#ff8844' - }; - return colors[type] || '#888888'; + ctx.fillText('Press TAB to return to garden', canvasWidth / 2 - 75, 55); } } ``` -## Map Transition Effect +## Mode Transition Effect ```javascript -// js/ui/MapTransition.js -export class MapTransition { +// js/ui/ModeTransition.js +export class ModeTransition { constructor() { this.progress = 0; - this.fromMap = null; - this.toMap = null; - } - - start(from, to) { - this.fromMap = from; - this.toMap = to; - this.progress = 0; - } - - update(deltaTime) { - this.progress = Math.min(1, this.progress + deltaTime * 3); + this.fromMode = null; + this.toMode = null; } - draw(ctx, canvasWidth, canvasHeight, playerX, playerY, cameraX, cameraY) { + draw(ctx, canvasWidth, canvasHeight) { if (this.progress <= 0) return; - const screenX = playerX - cameraX; - const screenY = playerY - cameraY; - - // Ripple effect from player position - const maxRadius = Math.max(canvasWidth, canvasHeight) * 1.5; - const currentRadius = maxRadius * this.progress; + // Leaf swirl transition + const leafCount = 20; + const maxRadius = Math.max(canvasWidth, canvasHeight); - ctx.save(); - - // Create circular reveal - ctx.beginPath(); - ctx.arc(screenX, screenY, currentRadius, 0, Math.PI * 2); - - // Color based on target map - if (this.toMap === 'creation') { - ctx.fillStyle = `rgba(0, 50, 30, ${0.5 * (1 - this.progress)})`; - } else { - ctx.fillStyle = `rgba(50, 0, 0, ${0.5 * (1 - this.progress)})`; + for (let i = 0; i < leafCount; i++) { + const angle = (i / leafCount) * Math.PI * 2 + this.progress * 3; + const radius = maxRadius * this.progress * (0.5 + (i % 3) * 0.2); + + const x = canvasWidth / 2 + Math.cos(angle) * radius; + const y = canvasHeight / 2 + Math.sin(angle) * radius; + + ctx.font = '24px Arial'; + ctx.fillText('🍃', x, y); } - ctx.fill(); - - // Edge glow - ctx.strokeStyle = this.toMap === 'creation' ? '#00ff88' : '#ff4444'; - ctx.lineWidth = 5; - ctx.stroke(); - - ctx.restore(); - } - - isComplete() { - return this.progress >= 1; + // Fade overlay + const alpha = Math.sin(this.progress * Math.PI) * 0.3; + ctx.fillStyle = this.toMode === 'planning' ? + `rgba(100, 150, 100, ${alpha})` : + `rgba(150, 100, 100, ${alpha})`; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); } } ``` @@ -468,29 +258,28 @@ export class MapTransition { ``` vampire-survivor/js/ ├── systems/ -│ ├── MapManager.js # NEW -│ └── PlantingSystem.js # NEW +│ ├── PlotManager.js +│ └── PlantingSystem.js ├── ui/ -│ ├── CreationMapUI.js # NEW -│ └── MapTransition.js # NEW +│ ├── PlanningModeUI.js +│ └── ModeTransition.js └── ... ``` ## Testing Checklist -- [ ] Map switching works with TAB key +- [ ] Mode switching works with TAB key - [ ] Transition effect plays smoothly -- [ ] Enemies pause in Creation Map +- [ ] Pests pause in Planning Mode - [ ] Seeds can be selected and planted - [ ] Invalid placements show red preview -- [ ] Planted seeds sync to PvE Map -- [ ] Aspect management works in Creation Map -- [ ] Alignment and enemy preview display correctly +- [ ] Planted seeds sync to Garden Mode +- [ ] Ability management works in Planning Mode ## Dependencies -- Phase 1-6 (all previous phases) +- Phase 1-6 ## Next Phase -[Phase 8: Polish and Balance](./08-phase-polish.md) - Final polish, hybrid aspects, and balance tuning. +[Phase 8: Polish and Balance](./08-phase-polish.md) diff --git a/plans/world-build-system/08-phase-polish.md b/plans/world-build-system/08-phase-polish.md index f451408..c17f2cc 100644 --- a/plans/world-build-system/08-phase-polish.md +++ b/plans/world-build-system/08-phase-polish.md @@ -2,286 +2,79 @@ ## Overview -Final phase focusing on hybrid aspects, visual polish, audio feedback, balance tuning, and overall game feel improvements. +Final phase focusing on hybrid abilities, visual polish, audio feedback, balance tuning, and overall game feel. ## Goals -- [ ] Implement hybrid Aspects (require mixed alignment) +- [ ] Implement hybrid Abilities (require mixed focus) - [ ] Add visual effects polish (particles, screen effects) -- [ ] Implement audio feedback system +- [ ] Implement audio feedback - [ ] Balance pass on all systems - [ ] Add tutorial hints - [ ] Performance optimization -- [ ] Bug fixes and edge cases -## Hybrid Aspects +## Hybrid Abilities -### Living Steel (40% Primal + 40% Forged) -- **Effect**: Armor regenerates 2/sec up to +20 bonus armor -- **Requirement**: Both Primal and Forged at 40%+ +### Pollinator's Gift (40% Flowers + 40% Vegetables) +- **Effect**: Bees heal you while attacking pests +- **Visual**: Golden bee swarm around player -### Storm Caller (40% Arcane + 40% Primal) -- **Effect**: Lightning spreads through vegetation, +100% damage in vegetation -- **Requirement**: Both Arcane and Primal at 40%+ +### Orchard Keeper (40% Flowers + 40% Trees) +- **Effect**: Fruit trees drop healing fruit automatically +- **Visual**: Falling blossoms and fruit -### Arcane Forge (40% Arcane + 40% Forged) -- **Effect**: Constructs gain energy shields (50% of health) -- **Requirement**: Both Arcane and Forged at 40%+ +### Forest Gardener (40% Vegetables + 40% Trees) +- **Effect**: Living walls regenerate and provide harvest bonus +- **Visual**: Vines and branches intertwining -### Elemental Mastery (30% each) -- **Effect**: +15% bonus to all damage types -- **Requirement**: All three alignments at 30%+ +### Master Gardener (30% each) +- **Effect**: +15% bonus to all garden types +- **Visual**: Rainbow garden aura -### World Shaper (50% any + 25% each other) -- **Effect**: Terrain features are 50% stronger -- **Requirement**: One dominant alignment with others balanced +## Visual Effects -## Hybrid Aspect System +### Particle Types -```javascript -// js/systems/HybridAspectSystem.js -export class HybridAspectSystem { - constructor(alignmentSystem) { - this.alignmentSystem = alignmentSystem; - } - - canUseHybridAspect(aspect) { - const alignment = this.alignmentSystem.getAlignment(); - - switch(aspect.id) { - case 'living_steel': - return alignment.primal >= 40 && alignment.forged >= 40; - case 'storm_caller': - return alignment.arcane >= 40 && alignment.primal >= 40; - case 'arcane_forge': - return alignment.arcane >= 40 && alignment.forged >= 40; - case 'elemental_mastery': - return alignment.arcane >= 30 && alignment.primal >= 30 && alignment.forged >= 30; - case 'world_shaper': - return this.checkWorldShaper(alignment); - default: - return true; - } - } - - checkWorldShaper(alignment) { - const values = [alignment.arcane, alignment.primal, alignment.forged]; - const max = Math.max(...values); - const others = values.filter(v => v !== max); - return max >= 50 && others.every(v => v >= 25); - } - - getHybridBonus(aspect, alignment) { - if (!this.canUseHybridAspect(aspect)) { - return 0; // Aspect disabled - } - - // Hybrid aspects get bonus based on how well requirements are met - // Exceeding requirements gives up to 50% bonus - return this.calculateExcessBonus(aspect, alignment); - } -} -``` - -## Visual Effects Polish - -### Particle System - -```javascript -// js/effects/ParticleSystem.js -export class ParticleSystem { - constructor() { - this.particles = []; - this.emitters = []; - } - - createBurst(x, y, color, count = 10) { - for (let i = 0; i < count; i++) { - const angle = (i / count) * Math.PI * 2; - const speed = 50 + Math.random() * 100; - - this.particles.push({ - x, y, - vx: Math.cos(angle) * speed, - vy: Math.sin(angle) * speed, - color, - size: 3 + Math.random() * 3, - life: 0.5 + Math.random() * 0.5, - maxLife: 1 - }); - } - } - - createTrail(x, y, color) { - this.particles.push({ - x: x + (Math.random() - 0.5) * 10, - y: y + (Math.random() - 0.5) * 10, - vx: (Math.random() - 0.5) * 20, - vy: -20 - Math.random() * 30, - color, - size: 2 + Math.random() * 2, - life: 0.3 + Math.random() * 0.3, - maxLife: 0.6 - }); - } - - update(deltaTime) { - for (let i = this.particles.length - 1; i >= 0; i--) { - const p = this.particles[i]; - - p.x += p.vx * deltaTime; - p.y += p.vy * deltaTime; - p.life -= deltaTime; - - // Gravity - p.vy += 100 * deltaTime; - - // Friction - p.vx *= 0.98; - p.vy *= 0.98; - - if (p.life <= 0) { - this.particles.splice(i, 1); - } - } - } - - draw(ctx, cameraX, cameraY) { - for (const p of this.particles) { - const screenX = p.x - cameraX; - const screenY = p.y - cameraY; - const alpha = p.life / p.maxLife; - - ctx.beginPath(); - ctx.arc(screenX, screenY, p.size * alpha, 0, Math.PI * 2); - ctx.fillStyle = p.color.replace('1)', `${alpha})`); - ctx.fill(); - } - } -} -``` +| Effect | Trigger | Visual | +|--------|---------|--------| +| Petal Burst | Flower harvest | Pink petals floating | +| Leaf Scatter | Vegetable harvest | Green leaves spinning | +| Bark Chips | Tree harvest | Brown wood chips | +| Pollen Cloud | Flower ability | Yellow sparkles | +| Bee Swarm | Pollinator Garden | Buzzing golden dots | +| Falling Fruit | Orchard | Colorful fruit dropping | ### Screen Effects -```javascript -// js/effects/ScreenEffects.js -export class ScreenEffects { - constructor() { - this.shakeAmount = 0; - this.shakeDecay = 5; - this.flashColor = null; - this.flashAlpha = 0; - } - - shake(amount) { - this.shakeAmount = Math.max(this.shakeAmount, amount); - } - - flash(color, alpha = 0.3) { - this.flashColor = color; - this.flashAlpha = alpha; - } - - update(deltaTime) { - // Decay shake - this.shakeAmount = Math.max(0, this.shakeAmount - this.shakeDecay * deltaTime); - - // Decay flash - this.flashAlpha = Math.max(0, this.flashAlpha - deltaTime * 2); - } - - getShakeOffset() { - if (this.shakeAmount <= 0) return { x: 0, y: 0 }; - - return { - x: (Math.random() - 0.5) * this.shakeAmount * 2, - y: (Math.random() - 0.5) * this.shakeAmount * 2 - }; - } - - drawFlash(ctx, canvasWidth, canvasHeight) { - if (this.flashAlpha <= 0) return; - - ctx.fillStyle = this.flashColor.replace('1)', `${this.flashAlpha})`); - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - } -} -``` +| Effect | Trigger | Visual | +|--------|---------|--------| +| Garden Bloom | Pure garden achieved | Flash of garden color | +| Pest Swarm | Large pest wave | Screen shake | +| Harvest Glow | Successful harvest | Brief golden overlay | -## Audio Feedback System +## Audio Feedback -```javascript -// js/audio/AudioManager.js -export class AudioManager { - constructor() { - this.sounds = {}; - this.musicVolume = 0.5; - this.sfxVolume = 0.7; - this.enabled = true; - } - - // Sound definitions (would load actual audio files) - soundDefinitions = { - harvest: { frequency: 440, duration: 0.1, type: 'sine' }, - hit: { frequency: 200, duration: 0.05, type: 'square' }, - levelUp: { frequency: 880, duration: 0.3, type: 'sine' }, - aspectDrop: { frequency: 660, duration: 0.2, type: 'triangle' }, - mapSwitch: { frequency: 330, duration: 0.15, type: 'sine' }, - pureBuild: { frequency: 1000, duration: 0.5, type: 'sine' } - }; - - play(soundName) { - if (!this.enabled) return; - - const def = this.soundDefinitions[soundName]; - if (!def) return; - - // Web Audio API implementation - const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); - const oscillator = audioCtx.createOscillator(); - const gainNode = audioCtx.createGain(); - - oscillator.type = def.type; - oscillator.frequency.setValueAtTime(def.frequency, audioCtx.currentTime); - - gainNode.gain.setValueAtTime(this.sfxVolume, audioCtx.currentTime); - gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + def.duration); - - oscillator.connect(gainNode); - gainNode.connect(audioCtx.destination); - - oscillator.start(); - oscillator.stop(audioCtx.currentTime + def.duration); - } - - playAlignmentSound(alignmentType) { - const frequencies = { - arcane: 660, - primal: 440, - forged: 330 - }; - - // Custom sound based on alignment - // ... - } -} -``` +| Sound | Trigger | +|-------|---------| +| Gentle chime | Plant watered | +| Satisfying pop | Plant harvested | +| Buzzing | Bees active | +| Bird chirp | Bird house attack | +| Rustling leaves | Mode transition | +| Victory fanfare | Pure garden achieved | ## Balance Configuration ```javascript -// js/config/BalanceConfig.js -export const BalanceConfig = { - // Neutral harvesting - neutrals: { +// js/config/GardenBalanceConfig.js +export const GardenBalanceConfig = { + plants: { fillRequired: 10, - harvestAlignmentGain: 5, - aspectDropRate: 0.20, - aspectDropRatePerFill: 0.01 + harvestFocusGain: 5, + abilityDropRate: 0.20 }, - // Alignment scaling - alignment: { + focus: { thresholds: { minor: 40, major: 60, @@ -291,240 +84,111 @@ export const BalanceConfig = { minor: 1.25, major: 1.5, pure: 2.0 - }, - penalties: { - minor: 0.9, - major: 0.75, - pure: 0.5 } }, - // Enemy spawning - enemies: { - baseSpawnRate: 2.0, // seconds - maxEnemies: 50, - spawnDistance: 400, + pests: { + baseSpawnRate: 2.0, + maxPests: 50, resistanceMultiplier: 0.5, weaknessMultiplier: 1.5 }, - // Terrain features terrain: { - energyPoolDamage: 2, - vegetationHeal: 1, - vegetationSlow: 0.2, - metalPlateSlow: 0.3, - turretDamage: 1, - turretFireRate: 1.0 + pollenCloudSlow: 0.3, + compostHeal: 2, + rootNetworkSlow: 0.5, + birdHouseDamage: 2 }, - // Aspects - aspects: { + abilities: { maxEquipped: 3, maxInventory: 10, hybridRequirement: 40 - }, - - // Dual map - maps: { - transitionDuration: 0.3, - seedGrowthTime: 60, - minSeedDistance: 100 } }; ``` -## Tutorial Hints System - -```javascript -// js/ui/TutorialHints.js -export class TutorialHints { - constructor() { - this.hints = [ - { id: 'first_neutral', text: 'Shoot neutrals to fill them, then walk over to harvest', shown: false }, - { id: 'alignment', text: 'Harvesting shifts world alignment - this affects enemies and abilities', shown: false }, - { id: 'aspect_drop', text: 'Aspects drop from harvested neutrals - equip them for power!', shown: false }, - { id: 'creation_map', text: 'Press TAB to enter Creation Mode and plan your strategy', shown: false }, - { id: 'pure_build', text: 'Reach 80% alignment for Pure Build bonuses!', shown: false }, - { id: 'hybrid_terrain', text: 'Place different neutral types near each other for hybrid effects', shown: false } - ]; - - this.currentHint = null; - this.hintTimer = 0; - this.hintDuration = 5; - } - - triggerHint(hintId) { - const hint = this.hints.find(h => h.id === hintId); - if (!hint || hint.shown) return; - - hint.shown = true; - this.currentHint = hint; - this.hintTimer = this.hintDuration; - } - - update(deltaTime) { - if (this.hintTimer > 0) { - this.hintTimer -= deltaTime; - if (this.hintTimer <= 0) { - this.currentHint = null; - } - } - } - - draw(ctx, canvasWidth, canvasHeight) { - if (!this.currentHint) return; - - const alpha = Math.min(1, this.hintTimer); - - // Hint box at top of screen - ctx.fillStyle = `rgba(0, 0, 0, ${0.8 * alpha})`; - ctx.fillRect(canvasWidth / 2 - 200, 60, 400, 40); - - ctx.fillStyle = `rgba(255, 255, 100, ${alpha})`; - ctx.font = '14px Arial'; - ctx.textAlign = 'center'; - ctx.fillText(this.currentHint.text, canvasWidth / 2, 85); - ctx.textAlign = 'left'; - } -} -``` - -## Performance Optimization +## Tutorial Hints -```javascript -// js/systems/PerformanceOptimizer.js -export class PerformanceOptimizer { - constructor() { - this.spatialHash = new Map(); - this.cellSize = 100; - this.frameTimeHistory = []; - this.targetFPS = 60; - } - - // Spatial hashing for collision detection - updateSpatialHash(entities) { - this.spatialHash.clear(); - - for (const entity of entities) { - const cellX = Math.floor(entity.x / this.cellSize); - const cellY = Math.floor(entity.y / this.cellSize); - const key = `${cellX},${cellY}`; - - if (!this.spatialHash.has(key)) { - this.spatialHash.set(key, []); - } - this.spatialHash.get(key).push(entity); - } - } - - getNearbyEntities(x, y, radius) { - const nearby = []; - const cellRadius = Math.ceil(radius / this.cellSize); - const centerCellX = Math.floor(x / this.cellSize); - const centerCellY = Math.floor(y / this.cellSize); - - for (let dx = -cellRadius; dx <= cellRadius; dx++) { - for (let dy = -cellRadius; dy <= cellRadius; dy++) { - const key = `${centerCellX + dx},${centerCellY + dy}`; - const cell = this.spatialHash.get(key); - if (cell) { - nearby.push(...cell); - } - } - } - - return nearby; - } - - // Adaptive quality based on performance - trackFrameTime(deltaTime) { - this.frameTimeHistory.push(deltaTime); - if (this.frameTimeHistory.length > 60) { - this.frameTimeHistory.shift(); - } - } - - getQualityLevel() { - const avgFrameTime = this.frameTimeHistory.reduce((a, b) => a + b, 0) / this.frameTimeHistory.length; - const currentFPS = 1 / avgFrameTime; - - if (currentFPS < 30) return 'low'; - if (currentFPS < 45) return 'medium'; - return 'high'; - } -} -``` - -## Final Testing Checklist +| Hint | Trigger | +|------|---------| +| "Water plants to fill them, then harvest!" | First plant spawns | +| "Different plants attract different pests" | First pest spawns | +| "Press TAB for Planning Mode" | After first harvest | +| "Plant companions together for bonuses!" | Two different plants nearby | +| "Reach 80% focus for Pure Garden power!" | Focus reaches 60% | -### Gameplay -- [ ] All neutral types work correctly -- [ ] All enemy types spawn and behave correctly -- [ ] Alignment system tracks and scales properly -- [ ] All terrain features function as designed -- [ ] All aspects work and scale with alignment -- [ ] Hybrid aspects activate at correct thresholds -- [ ] Dual map switching works smoothly -- [ ] Seed planting and growth works +## Testing Checklist -### Balance -- [ ] Pure builds feel powerful but challenging -- [ ] Hybrid builds are viable alternatives -- [ ] Enemy resistance creates interesting gameplay -- [ ] Terrain features provide meaningful advantage -- [ ] Aspect drops feel rewarding -- [ ] Difficulty scales appropriately over time - -### Polish +- [ ] Hybrid abilities activate at correct thresholds - [ ] Particle effects enhance feedback -- [ ] Screen shake feels impactful - [ ] Audio cues are clear and satisfying - [ ] Tutorial hints appear at right times -- [ ] UI is clear and readable -- [ ] Transitions are smooth - -### Performance -- [ ] Maintains 60 FPS with 50 enemies -- [ ] No memory leaks over extended play -- [ ] Spatial hashing improves collision checks -- [ ] Quality adapts to device capability +- [ ] Game feels balanced and fun +- [ ] Performance stays smooth with many entities -## File Structure After Phase 8 +## Final File Structure ``` vampire-survivor/js/ ├── entities/ -│ ├── aspects/ +│ ├── plants/ +│ │ ├── Plant.js +│ │ ├── FlowerBud.js +│ │ ├── VeggieSprout.js +│ │ ├── Sapling.js +│ │ └── hybrid/ +│ ├── pests/ +│ │ ├── Pest.js +│ │ ├── flower/ +│ │ ├── vegetable/ +│ │ └── tree/ +│ ├── terrain/ +│ │ ├── GardenFeature.js +│ │ ├── flower/ +│ │ ├── vegetable/ +│ │ ├── tree/ │ │ └── hybrid/ -│ │ ├── LivingSteel.js # NEW -│ │ ├── StormCaller.js # NEW -│ │ ├── ArcaneForge.js # NEW -│ │ ├── ElementalMastery.js # NEW -│ │ └── WorldShaper.js # NEW +│ └── abilities/ +│ ├── Ability.js +│ ├── flower/ +│ ├── vegetable/ +│ ├── tree/ +│ └── hybrid/ +├── systems/ +│ ├── GardenFocusSystem.js +│ ├── PestSpawnSystem.js +│ ├── TerrainSystem.js +│ ├── AbilityManager.js +│ ├── AbilityScalingSystem.js +│ ├── CompanionPlantingSystem.js +│ ├── PlotManager.js +│ └── PlantingSystem.js ├── effects/ -│ ├── ParticleSystem.js # NEW -│ └── ScreenEffects.js # NEW +│ ├── GardenParticles.js +│ └── ScreenEffects.js ├── audio/ -│ └── AudioManager.js # NEW -├── systems/ -│ ├── HybridAspectSystem.js # NEW -│ └── PerformanceOptimizer.js # NEW +│ └── GardenAudio.js ├── ui/ -│ └── TutorialHints.js # NEW -├── config/ -│ └── BalanceConfig.js # NEW -└── ... +│ ├── GardenFocusUI.js +│ ├── AbilityUI.js +│ ├── PlanningModeUI.js +│ ├── CompanionVisuals.js +│ └── TutorialHints.js +└── config/ + └── GardenBalanceConfig.js ``` ## Conclusion -This completes the World Build System implementation plan. The system transforms neutral mobs from simple obstacles into a deep strategic layer that: +The Garden Defense System transforms the vampire-survivor gameplay into a charming gardening experience where: -1. **Terraforms the world** through harvesting choices -2. **Shifts alignment** affecting abilities and enemies -3. **Creates ecosystem interactions** between neutral types -4. **Rewards build commitment** with powerful synergies -5. **Provides dual-map gameplay** for planning and combat +1. **Three plant types** (Flowers, Vegetables, Trees) create strategic choices +2. **Realistic pests** (Aphids, Rabbits, Beetles) provide thematic enemies +3. **Garden terrain** (Pollen clouds, Scarecrows, Bird houses) adds tactical depth +4. **Gardener abilities** scale with garden focus for build diversity +5. **Companion planting** rewards thoughtful plant placement +6. **Dual plot system** provides peaceful planning alongside action -The 8-phase implementation allows incremental development while maintaining a playable game at each stage. +The 8-phase implementation allows incremental development while maintaining a playable, cohesive garden defense game.