22 KiB
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.
Getting Started
-
Create a directory under
levels/
with your level pack name:levels/My Cool Levels/
-
Start with
1.json
in your new directory. Each level after that is numbered in order:2.json
,3.json
, etc.
Basic Level Structure
Every level needs these essential properties:
{
"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:
{
"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 totrue
to let players skip the dialogue sequenceentries
: Array of dialogue entries that play in sequencespeaker
: Name of the character speaking (optional for narrative entries)text
: The dialogue text to be spokennarrative
: Set totrue
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 playsounds/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
{
"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
{
"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"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
{
"x": 35,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"zombie_spawn_chance": 20,
"item": "shin_bone"
}
Grave items available:
"shin_bone"
- Currency for extra lives (100 needed)"guts"
- Health upgrade/restoration- 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
{
"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 enteredturn_threshold
: How quickly hunting enemies turn around to attack (lower = more aggressive)
Advanced Enemy Behaviors
Vulnerability System (Ghost-like Enemies)
{
"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
{
"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 behaviorspawn_type
: Type of enemy to spawn (e.g., "zombie", "black_cat")spawn_cooldown
: Milliseconds between spawn attemptsspawn_chance
: 0-100 probability of spawning when cooldown expiresspawn_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
{
"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
{
"x": 55,
"y": 0,
"type": "catapult",
"fire_interval": 4000,
"range": 25
}
Properties:
fire_interval
: Milliseconds between shotsrange
: How far the catapult can shoot
Spider Web
{
"type": "spider_web",
"x": 15,
"y": 0
}
Slows player movement and attack speed temporarily when walked over. Automatically spawns a spider enemy at the web location.
Grasping Hands
{
"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:
{
"type": "spider_web",
"x": 25,
"y": 3
}
Setup in your level pack's sound directory:
- Replace
spiderweb.ogg
withcoffin.ogg
(ambient coffin sound) - Replace
hit_spiderweb.ogg
withcoffin_shatter.ogg
(break sound) - Replace
spider.ogg
withmummy.ogg
(spawned enemy sound) - Replace
spider_dies.ogg
withmummy_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. The enemy is internally still a spider but sounds and feels like a mummy emerging from a sarcophagus. The slow down for the player can be because the skeleton is tangled in the mummy wrappings, or perhapse he's tangled in the shattered wood. It also does not float, so players would quickly learn to be war, maybe this coffin is one to just be passed under (low hanging).
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 aliveenemy_dies.ogg
- The death sound
For example, to add a werewolf enemy:
- Add
werewolf.ogg
andwerewolf_dies.ogg
to the sounds directory - Use
"werewolf"
as theenemy_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:
{
"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 attackinghit_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.
// 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": "decorated witch's broom" },
"nunchucks": { "name": "candy cane nunchucks" }
}
}
// 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:
{
"x": 25,
"y": 0,
"type": "catapult",
"fire_interval": 3000,
"range": 30,
"sound_overrides": {
"base": "snowball_launcher",
"launch": "snowball_launcher_launch"
}
}
{
"x_range": [40, 60],
"y": 0,
"type": "grasping_hands",
"delay": 1000,
"sound_overrides": {
"base": "avalanche"
}
}
{
"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
:
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:
{
"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:
{
"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!