# Creating Custom Levels for Wicked Quest Want to add your own levels? It's simple! Every level is a JSON file, and you can create as many as you want up to 998. Level 999 is reserved for survival mode. ## Getting Started 1. Create a directory under `levels/` with your level pack name: ``` levels/My Cool Levels/ ``` 2. Start with `1.json` in your new directory. Each level after that is numbered in order: `2.json`, `3.json`, etc. You can also add instructions.txt and credits.txt to the level pack. These items will be shown in the menu for the level pack, e.g. Story Mode, Survival Mode, Instructions, Credits. ## Basic Level Structure Every level needs these essential properties: ```json { "level_id": 1, "name": "My Level", "description": "Set the scene for your level.", "locked": false, "player_start": { "x": 0, "y": 0 }, "boundaries": { "left": 0, "right": 200 }, "ambience": "Graveyard Blitz.ogg", "footstep_sound": "footstep_stone" } ``` If you set `locked` to `true`, the player cannot leave the level until all enemies have been defeated. Drop custom ambience files (e.g., music or creepy soundtracks) in `sounds/ambience/`. Add custom footstep sounds into the `sounds/` directory. ### Dialogue System Instead of using a simple `"description"` field, levels can now include interactive dialogue sequences with the new `"dialog"` property. This creates immersive story-driven introductions with character conversations: ```json { "level_id": 1, "name": "Story Level", "dialog": { "allow_skip": true, "entries": [ { "speaker": "Character Name", "text": "This is what the character says." }, { "speaker": "Billy Bones", "text": "This is the player character's response." }, { "text": "A narrative description of what happens.", "narrative": true }, { "speaker": "Character Name", "text": "More dialogue with optional sound effect.", "sound": "character_sound" } ] }, "player_start": { "x": 0, "y": 0 } } ``` **Dialogue Properties:** - `allow_skip`: Set to `true` to let players skip the dialogue sequence - `entries`: Array of dialogue entries that play in sequence - `speaker`: Name of the character speaking (optional for narrative entries) - `text`: The dialogue text to be spoken - `narrative`: Set to `true` for descriptive text entries (no speaker) - `sound`: Optional sound file to play with this dialogue entry. If no sound is specified, the system will automatically play `sounds/dialogue.ogg` if it exists. **Note:** Levels can include both `"description"` and `"dialog"`. When both are present, the dialogue plays first, followed by the standard level description message format ("Level X, Name. Description."). ## Adding Objects All objects go in an `"objects"` list. Here are examples of what you can add: ### Object Positioning Objects can be positioned using either: - `"x": 15` - Single position for objects like coffins, graves, catapults - `"x_range": [10, 20]` - Range for objects that span multiple positions like bone dust collections, enemy patrol areas, or hazards The `y` coordinate determines the vertical layer: - `y: 0` - Ground level (enemies, graves, catapults) - `y: 3` - Elevated level (bone dust, coffins) - `y: 12` - High level (skull storms) ### Collectibles #### Bone Dust ```json { "x_range": [5, 8], "y": 3, "sound": "bone_dust", "collectible": true, "static": true } ``` The `static` property means objects don't move - they stay in their fixed positions. Static objects like bone dust and graves remain stationary, while enemies without this property can move and patrol. ### Interactive Objects #### Coffins ```json { "x": 15, "y": 3, "sound": "coffin", "type": "coffin", "item": "extra_life" } ``` **Items available for coffins:** - `"extra_life"` - Grants an extra life - `"hand_of_glory"` - Provides temporary invincibility - `"jack_o_lantern"` - Throwable projectile - `"guts"` - Increases max health or restores health if at max - `"cauldron"` - Special item - `"witch_broom"` - Weapon upgrade - `"shin_bone"` - Currency for extra lives (100 needed) - `"guts"` - Health upgrade/restoration - `"random"` - Randomly selects from eligible items (default if not specified) **Coffin Behavior:** When broken with any weapon, coffins drop their specified item 1-2 tiles away in a random direction. The item bounces and can be collected by walking over it. #### Graves ```json { "x": 35, "y": 0, "type": "grave", "sound": "grave", "static": true, "zombie_spawn_chance": 20, "item": "shin_bone" } ``` **Grave items available:** - Any item available for coffins **Grave behaviors:** - `zombie_spawn_chance`: 0-100, higher means more zombies spawn when walked over - **Item collection:** Duck while walking (not running) with the rusty shovel equipped to safely collect items - **Grave filling:** Duck with shovel over empty graves (no item) to fill them and remove the hazard ### Enemies ```json { "x_range": [20, 35], "y": 0, "enemy_type": "goblin", "health": 4, "damage": 2, "attack_range": 1.5, "attack_pattern": { "type": "hunter", "turn_threshold": 5 } } ``` **Attack patterns:** - `"patrol"`: Enemy moves back and forth within their x_range - `"hunter"`: Enemy will leave their area to pursue the player once entered - `turn_threshold`: How quickly hunting enemies turn around to attack (lower = more aggressive) #### Advanced Enemy Behaviors ##### Vulnerability System (Ghost-like Enemies) ```json { "x_range": [400, 415], "y": 0, "enemy_type": "ghost", "health": 60, "damage": 2, "attack_range": 1, "has_vulnerability": true, "is_vulnerable": false, "vulnerability_duration": 3000, "invulnerability_duration": 5000, "speed_multiplier": 0.8, "attack_cooldown": 1200, "attack_pattern": { "type": "hunter", "turn_threshold": 2 } } ``` **Vulnerability system:** Enemy alternates between vulnerable and invulnerable states. They can only be damaged when vulnerable (plays `enemy_is_vulnerable.ogg` sound). ##### Enemy Spawning System ```json { "x_range": [420, 470], "y": 0, "enemy_type": "revenant", "health": 80, "damage": 1, "attack_range": 1, "attack_pattern": { "type": "patrol" }, "can_spawn": true, "spawn_type": "zombie", "spawn_cooldown": 2500, "spawn_chance": 75, "spawn_distance": 5 } ``` **Spawning properties:** - `can_spawn`: Enable spawning behavior - `spawn_type`: Type of enemy to spawn (e.g., "zombie", "black_cat") - `spawn_cooldown`: Milliseconds between spawn attempts - `spawn_chance`: 0-100 probability of spawning when cooldown expires - `spawn_distance`: How far from the spawner to place new enemies **Example combinations:** - **Witch spawning black cats:** High health witch that periodically spawns fast black cats - **Revenant spawning zombies:** Tough enemy that creates zombie minions - **Any enemy type can spawn any other enemy type** ### Hazards #### Skull Storm ```json { "x_range": [40, 60], "y": 12, "type": "skull_storm", "damage": 4, "maximum_skulls": 2, "frequency": { "min": 1, "max": 4 } } ``` The `maximum_skulls` setting controls how many skulls can be falling simultaneously. `frequency` is the number of seconds that can elapse before the next skull falls. #### Catapult ```json { "x": 55, "y": 0, "type": "catapult", "fire_interval": 4000, "range": 25 } ``` **Properties:** - `fire_interval`: Milliseconds between shots - `range`: How far the catapult can shoot #### Spider Web ```json { "type": "spider_web", "x": 15, "y": 3, "sound_overrides": { "base": "floating_trap", "hit_sound": "trap_trigger", "spawn_enemy": "elf" } } ``` **Spider Web Mechanics:** Spider webs create interactive traps that can slow player movement and attack speed temporarily when triggered. They automatically spawn an enemy at the web location. When triggered, the player bounces back 3 tiles and may receive a 15-second penalty (half movement speed, double attack time) depending on the slowdown setting. **Y-Position Based Behavior:** - **`"y": 0` (Ground Level)** - Ground trap that triggers when walking/running. **Avoid by jumping over it.** - **`"y": > 0` (Air Level)** - Air trap that triggers when walking or jumping. **Avoid by ducking under it.** **Sound Override Properties:** - `"base"`: Override the ambient web sound (e.g., "floating_trap", "bear_trap") - `"hit_sound"`: Override the trigger sound (e.g., "trap_trigger", "snap") - `"spawn_enemy"`: Override spawned enemy type ("spider", "elf", "witch") - `"enemy_stats"`: Override spawned enemy stats (health, damage, speed_multiplier, attack_range) - `"slowdown"`: Override slowdown effect (true/false, defaults to true) **Level Design Guidelines:** - **For traditional spider webs, always use `"y": > 0`** (air placement) unless you're specifically creating a themed ground trap - **Ground webs** (`"y": 0`) work well for: bear traps, tripwires, pressure plates, snowdrifts, present piles - **Air webs** (`"y": > 0`) work well for: hanging webs, floating pods, magical traps, dangling ornaments - **Slowdown considerations**: Use `"slowdown": false` for traps where the slowdown effect doesn't make thematic sense (stepping on a snowdrift shouldn't entangle you like a web would) **Example Themed Variations:** note that in true json you cannot use comments like the // lines here for explanations. ```json // Christmas snowdrift (ground trap - jump to avoid) { "type": "spider_web", "x": 50, "y": 0, "sound_overrides": { "base": "snow_drift", "hit_sound": "snow_drift_hit", "spawn_enemy": "abominable_snowman", "enemy_stats": { "health": 12, "damage": 3, "speed_multiplier": 0.8, "attack_range": 1 }, "slowdown": false } } // Floating snow cloud (air trap - duck to avoid) { "type": "spider_web", "x": 75, "y": 4, "sound_overrides": { "base": "floating_snow_cloud", "hit_sound": "cloud_burst", "spawn_enemy": "witch", "slowdown": true } } // Traditional spider web (default behavior) { "type": "spider_web", "x": 100, "y": 2 } ``` #### Grasping Hands ```json { "x_range": [40, 60], "y": 0, "type": "grasping_hands", "delay": 1000, "crumble_speed": 0.065 } ``` The ground crumbles beneath the player as undead hands reach up. The `delay` is in milliseconds before crumbling starts, and `crumble_speed` controls how fast the crumbling catches up to the player. ## Level System Flexibility The level system supports complex interactions and behaviors: ### Dynamic Item Drops - **Monsters spawning items:** Use enemy spawning system to create enemies that drop items when killed - **Graves with random loot:** Each grave can contain different items collected via specific mechanics ### Creative Design Techniques #### Coffins That Release Enemies While not directly supported, you can create the illusion of coffins that release enemies when touched: ```json { "type": "spider_web", "x": 25, "y": 3, "sound_overrides": { "base": "coffin", "hit_sound": "coffin_shatter", "spawn_enemy": "elf" } } ``` **Modern Approach (Recommended):** Use sound overrides directly in your JSON for cleaner, more maintainable theming. The above creates a "coffin" that shatters and spawns an enemy when touched. **Legacy Approach:** You can still replace sounds in your level pack's sound directory: - Replace `spiderweb.ogg` with `coffin.ogg` (ambient coffin sound) - Replace `hit_spiderweb.ogg` with `coffin_shatter.ogg` (break sound) - Replace `spider.ogg` with `mummy.ogg` (spawned enemy sound) - Replace `spider_dies.ogg` with `mummy_dies.ogg` (death sound) **Result:** Players hear a coffin, when they touch it they hear it shatter, get slowed down (representing the enemy emerging), and a "mummy" automatically spawns from the coffin. Since this uses `"y": 3` (air level), players must duck to avoid triggering it - creating the "low hanging coffin" effect you described. ### Complex Enemy Combinations - **Spawner + Vulnerability:** Create a tough ghost that spawns minions while alternating between vulnerable states - **Multi-layered encounters:** Combine patrol enemies with hunters and spawners for dynamic battles - **Progressive difficulty:** Later waves can spawn stronger enemy types ### Environmental Storytelling - **Themed hazard combinations:** Skull storms over graveyards, catapults defending strategic chokepoints - **Sound design integration:** Custom footsteps and ambience per level ## Creating New Enemy Types Want to add a new enemy type? You'll need two sound files in the `sounds/` directory: - `enemy.ogg` - The sound the enemy makes while alive - `enemy_dies.ogg` - The death sound For example, to add a werewolf enemy: - Add `werewolf.ogg` and `werewolf_dies.ogg` to the sounds directory - Use `"werewolf"` as the `enemy_type` in your level file ## Level Design Tips ### Balance Guidelines - **Add at least 33 bone dust per level** (for extra life economy) - **Space out hazards** to give players a chance to react - **Enemy health recommendations:** - Regular enemies: 4-6 HP - Mini-bosses: 8-15 HP - Bosses: 40+ HP - **Lock boss levels** with `"locked": true` ### Advanced Design Patterns - **Layered challenges:** Combine moving enemies with static hazards - **Risk/reward decisions:** Place valuable items near dangerous enemies - **Tactical positioning:** Use terrain and enemy placement to create strategic choices - **Escalating difficulty:** Gradually introduce new mechanics and combinations ## Custom Level Pack Structure You can keep your levels and sounds separate from the main game. Create a directory structure like this for a pack called "Samhain Showdown": ### Directory Structure ``` levels/Samhain Showdown/ ├── 1.json ├── 2.json ├── 3.json └── end.ogg sounds/Samhain Showdown/ ├── custom_footstep.ogg ├── werewolf.ogg └── werewolf_dies.ogg sounds/Samhain Showdown/ambience ├── werewolf_hunting.ogg ├── howling_winds.ogg ``` ### Advanced Sound Override System Beyond simple file replacement, Wicked Quest now supports granular sound overrides directly in your level JSON files. This allows thematic consistency where a catapult becomes a "snowball launcher" or grasping hands become an "avalanche" - same mechanics, different sounds and feel. #### Weapon Sound Overrides Override weapon sounds and names globally for an entire level: ```json { "level_id": 1, "name": "Winter Wonderland", "weapon_sound_overrides": { "rusty_shovel": { "name": "rusty snow shovel", "attack_sound": "player_snow_shovel_attack", "hit_sound": "player_snow_shovel_hit" }, "nunchucks": { "name": "ice sickles", "attack_sound": "player_ice_sickles_attack", "hit_sound": "player_ice_sickles_hit" }, "witch_broom": { "name": "snow broom", "attack_sound": "player_snow_broom_attack", "hit_sound": "player_snow_broom_hit" } } } ``` **Weapon Override Properties:** - `name`: Display name for the weapon (e.g., "ice sickles" instead of "nunchucks") - `attack_sound`: Sound played when attacking - `hit_sound`: Sound played when hitting an enemy **Weapon Override Persistence:** Weapon overrides set in **level 1** automatically persist throughout the entire level pack! You only need to define `weapon_sound_overrides` in your first level (1.json) and all subsequent levels will inherit these themed weapon names. ```json // In levels/Wicked Christmas/1.json - Sets theme for entire pack { "level_id": 1, "weapon_sound_overrides": { "rusty_shovel": { "name": "rusty snow shovel" }, "witch_broom": { "name": "snow broom" }, "nunchucks": { "name": "ice sickles" } } } // In levels/Wicked Christmas/2.json - No overrides needed! { "level_id": 2, // Weapons automatically use themed names from level 1 } ``` **How it works:** - Level 1 overrides persist across all levels in the same play session - Switching to a different level pack resets weapons to their original names - Later levels can still override weapons if you want to change themes mid-pack - The system stores the original weapon names internally for proper lookups #### Object Sound Overrides Override sounds for individual objects in your level: ```json { "x": 25, "y": 0, "type": "catapult", "fire_interval": 3000, "range": 30, "sound_overrides": { "base": "snowball_launcher", "launch": "snowball_launcher_launch" } } ``` ```json { "x_range": [40, 60], "y": 0, "type": "grasping_hands", "delay": 1000, "sound_overrides": { "base": "avalanche" } } ``` ```json { "x": 35, "y": 0, "type": "grave", "item": "shin_bone", "sound_overrides": { "base": "snow_mound", "item": "candy_cane" } } ``` **Object Sound Override Properties:** - `base`: Override the main ambient sound (e.g., "catapult" → "snowball_launcher") - `launch`: Override launch sound for catapults (e.g., "catapult_launch" → "snowball_launcher_launch") - `item`: Override pickup sound for grave items (e.g., "get_shin_bone.ogg" → "get_candy_cane.ogg") - `warning_message`: Override warning message for grasping hands (e.g., "The ground crumbles as snow begins to avalanche!") - `death_message`: Override death message for grasping hands (e.g., "You vanish under tons of snow!") #### Themed Item Equivalents The sound override system includes intelligent item mapping for crafting consistency. Certain themed items automatically behave like their original counterparts: **Christmas Theme:** - `"candy_cane"` → Functions as `"shin_bone"` (increments shin bone count) - `"reindeer_guts"` → Functions as `"guts"` (enables nunchucks crafting) **Result:** Collecting 2 candy canes + reindeer guts = nunchucks (can be renamed to "ice sickles") This system allows complete thematic consistency where players collect "2 Candy Canes + Reindeer Guts = Ice Sickles" while preserving all original game mechanics. The mapping works automatically across any level pack - simply use themed item names and they'll function correctly. **Adding New Themed Equivalents:** To add your own themed items, modify the `themed_mappings` in `src/powerup.py`: ```python themed_mappings = { "your_bone_item": "shin_bone", "your_guts_item": "guts", } ``` #### Complete Thematic Example Here's how to transform a Halloween level into a Christmas level using sound overrides: ```json { "level_id": 1, "name": "Winter Siege", "description": "Santa's workshop is under attack by snow witches!", "weapon_sound_overrides": { "rusty_shovel": { "name": "snow shovel", "attack_sound": "player_snow_shovel_attack", "hit_sound": "player_snow_shovel_hit" } }, "objects": [ { "x": 25, "y": 0, "type": "catapult", "sound_overrides": { "base": "snowball_launcher", "launch": "snowball_launcher_launch" } }, { "x_range": [40, 60], "y": 12, "type": "skull_storm", "sound_overrides": { "base": "snowball_storm" } }, { "x": 75, "y": 0, "type": "grave", "item": "shin_bone", "sound_overrides": { "base": "snow_pile", "item": "candy_cane" } }, { "x_range": [90, 110], "y": 0, "type": "grasping_hands", "sound_overrides": { "base": "avalanche", "warning_message": "The ground crumbles as snow begins to avalanche!", "death_message": "You vanish under tons of snow!" } } ] } ``` **Result:** - Weapons sound winter-themed when attacking - "Catapult" becomes "Snowball Launcher" with appropriate launch sounds - "Skull Storm" becomes "Snowball Storm" - "Graves" become "Snow Piles" containing "Candy Canes" instead of "Shin Bones" - "Grasping Hands" becomes "Avalanche" with snow-themed death messages - All mechanics remain identical - only audio and messaging changes #### Legacy Sound Override System - **Custom ambience:** Place in `sounds/[Pack Name]/ambience/` - **Custom enemy sounds:** Place in `sounds/[Pack Name]/` - **Custom footsteps:** Reference in level JSON as `"footstep_sound"` - **Ending scene:** Add `end.ogg` in the level pack directory This legacy system allows complete audio customization through file replacement. For example, skull storms could become firestorms just by replacing the skull storm sounds in your pack's sound directory. ## Complete Example Level Here's a comprehensive example showing multiple advanced features: ```json { "level_id": 5, "name": "The Witch's Domain", "dialog": { "allow_skip": true, "entries": [ { "speaker": "Ancient Spirit", "text": "Mortal skeleton, you dare enter the witch's sacred domain?", "sound": "ancient_spirit" }, { "speaker": "Billy Bones", "text": "I'm looking for trouble, and I think I found it." }, { "text": "The air grows thick with dark magic as ancient runes begin to glow.", "narrative": true, "sound": "magic_ambience" }, { "speaker": "Ancient Spirit", "text": "Then face the consequences of your boldness. The witch's minions will not show mercy.", "sound": "evil_laughter" }, { "speaker": "Billy Bones", "text": "Good thing I don't need any." } ] }, "locked": true, "player_start": {"x": 0, "y": 0}, "boundaries": {"left": 0, "right": 300}, "ambience": "witch_theme.ogg", "footstep_sound": "footstep_mud", "objects": [ { "x_range": [5, 15], "y": 3, "sound": "bone_dust", "collectible": true, "static": true }, { "x": 25, "y": 3, "type": "coffin", "item": "hand_of_glory" }, { "x": 40, "y": 0, "type": "grave", "zombie_spawn_chance": 50, "item": "guts" }, { "x_range": [60, 80], "y": 0, "type": "grasping_hands", "delay": 500, "crumble_speed": 0.08 }, { "x": 100, "y": 0, "type": "catapult", "fire_interval": 3000, "range": 30 }, { "x_range": [120, 140], "y": 12, "type": "skull_storm", "damage": 3, "maximum_skulls": 3, "frequency": {"min": 2, "max": 5} }, { "x_range": [200, 250], "y": 0, "enemy_type": "ghost", "health": 40, "damage": 2, "attack_range": 1.5, "has_vulnerability": true, "vulnerability_duration": 2000, "invulnerability_duration": 4000, "can_spawn": true, "spawn_type": "zombie", "spawn_cooldown": 3000, "spawn_chance": 60, "spawn_distance": 4, "attack_pattern": {"type": "hunter", "turn_threshold": 3} }, { "x_range": [260, 290], "y": 0, "enemy_type": "witch", "health": 60, "damage": 3, "attack_range": 2, "can_spawn": true, "spawn_type": "black_cat", "spawn_cooldown": 4000, "spawn_chance": 80, "spawn_distance": 6, "attack_pattern": {"type": "patrol"} } ] } ``` This example demonstrates: - **Environmental hazards** (grasping hands, skull storm, catapult) - **Interactive elements** (coffin with invincibility, grave with health) - **Advanced enemies** (vulnerable zombie-spawning ghost, cat-spawning witch) - **Custom audio** (themed ambience and footsteps) - **Strategic design** (safe zones, risk/reward placement) Check out the existing Wicked Quest levels for more examples and inspiration!