Make the game more level creator friendly by separating levels into adventures. Menu for choosing which adventure you want.
This commit is contained in:
178
levels/2.json
178
levels/2.json
@@ -1,178 +0,0 @@
|
||||
{
|
||||
"level_id": 2,
|
||||
"name": "The Graveyard",
|
||||
"description": "The mausoleum led to an ancient graveyard. Watch out for falling skulls!",
|
||||
"player_start": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"x": 5,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x_range": [15, 20],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [21, 29],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 2,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 35,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 10
|
||||
},
|
||||
{
|
||||
"x_range": [33, 38],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [45, 60],
|
||||
"y": 12,
|
||||
"type": "skull_storm",
|
||||
"damage": 3,
|
||||
"maximum_skulls": 2,
|
||||
"frequency": {
|
||||
"min": 3,
|
||||
"max": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 55,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin",
|
||||
"item": "guts"
|
||||
},
|
||||
{
|
||||
"x_range": [65, 70],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [71, 79],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 2,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 85,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x": 95,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 10
|
||||
},
|
||||
{
|
||||
"x_range": [105, 108],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 120,
|
||||
"y": 0,
|
||||
"type": "catapult",
|
||||
"fire_interval": 5000,
|
||||
"range": 15
|
||||
},
|
||||
{
|
||||
"x_range": [130, 165],
|
||||
"y": 15,
|
||||
"type": "skull_storm",
|
||||
"damage": 4,
|
||||
"maximum_skulls": 3,
|
||||
"frequency": {
|
||||
"min": 2,
|
||||
"max": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [145, 150],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 160,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 10
|
||||
},
|
||||
{
|
||||
"x": 170,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin",
|
||||
"item": "extra_life"
|
||||
},
|
||||
{
|
||||
"x_range": [175, 180],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 185,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 10
|
||||
},
|
||||
{
|
||||
"x_range": [189, 198],
|
||||
"y": 0,
|
||||
"enemy_type": "witch",
|
||||
"health": 2,
|
||||
"damage": 2,
|
||||
"attack_range": 1.5,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
}
|
||||
],
|
||||
"boundaries": {
|
||||
"left": 0,
|
||||
"right": 200
|
||||
},
|
||||
"footstep_sound": "footstep_tall_grass"
|
||||
}
|
252
levels/3.json
252
levels/3.json
@@ -1,252 +0,0 @@
|
||||
{
|
||||
"level_id": 3,
|
||||
"name": "Endless Graves",
|
||||
"description": "Graves continue in all directions as far as you can see. The dead seem restless.",
|
||||
"player_start": {
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"x_range": [1, 4],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 5,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x_range": [6, 10],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 15,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 25
|
||||
},
|
||||
{
|
||||
"x_range": [25, 28],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [21, 31],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 3,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [35, 50],
|
||||
"y": 12,
|
||||
"type": "skull_storm",
|
||||
"damage": 3,
|
||||
"maximum_skulls": 2,
|
||||
"frequency": {
|
||||
"min": 2,
|
||||
"max": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [40, 44],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 55,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 25
|
||||
},
|
||||
{
|
||||
"x_range": [60, 70],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 3,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [75, 78],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [71, 81],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 3,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 85,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 28
|
||||
},
|
||||
{
|
||||
"x": 85,
|
||||
"y": 3,
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x_range": [95, 120],
|
||||
"y": 15,
|
||||
"type": "skull_storm",
|
||||
"damage": 3,
|
||||
"maximum_skulls": 2,
|
||||
"frequency": {
|
||||
"min": 2,
|
||||
"max": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [105, 115],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x_range": [101, 111],
|
||||
"y": 0,
|
||||
"enemy_type": "witch",
|
||||
"health": 6,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 125,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 28
|
||||
},
|
||||
{
|
||||
"x": 135,
|
||||
"y": 0,
|
||||
"hazard": true,
|
||||
"sound": "grave",
|
||||
"static": true,
|
||||
"zombie_spawn_chance": 30
|
||||
},
|
||||
{
|
||||
"x_range": [140, 150],
|
||||
"y": 0,
|
||||
"enemy_type": "goblin",
|
||||
"health": 3,
|
||||
"damage": 2,
|
||||
"attack_range": 1,
|
||||
"attack_pattern": {
|
||||
"type": "patrol"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [155, 158],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 145,
|
||||
"y": 3,
|
||||
"item": "hand_of_glory",
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x_range": [146, 166],
|
||||
"y": 0,
|
||||
"enemy_type": "ghoul",
|
||||
"health": 10,
|
||||
"damage": 3,
|
||||
"attack_range": 2,
|
||||
"attack_pattern": {
|
||||
"type": "hunter",
|
||||
"turn_threshold": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"x_range": [165, 190],
|
||||
"y": 15,
|
||||
"type": "skull_storm",
|
||||
"damage": 4,
|
||||
"maximum_skulls": 3,
|
||||
"frequency": {
|
||||
"min": 2,
|
||||
"max": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"x": 175,
|
||||
"y": 0,
|
||||
"type": "catapult",
|
||||
"fire_interval": 4500,
|
||||
"range": 20
|
||||
},
|
||||
{
|
||||
"x_range": [173, 176],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
},
|
||||
{
|
||||
"x": 185,
|
||||
"y": 3,
|
||||
"item": "extra_life",
|
||||
"sound": "coffin",
|
||||
"type": "coffin"
|
||||
},
|
||||
{
|
||||
"x_range": [190, 195],
|
||||
"y": 3,
|
||||
"sound": "coin",
|
||||
"collectible": true,
|
||||
"static": true
|
||||
}
|
||||
],
|
||||
"boundaries": {
|
||||
"left": 0,
|
||||
"right": 200
|
||||
},
|
||||
"footstep_sound": "footstep_tall_grass"
|
||||
}
|
133
src/game_selection.py
Normal file
133
src/game_selection.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import os
|
||||
import time
|
||||
import pygame
|
||||
from os.path import isdir, join
|
||||
from libstormgames import speak
|
||||
|
||||
def get_available_games():
|
||||
"""Get list of available game directories in levels folder.
|
||||
|
||||
Returns:
|
||||
list: List of game directory names
|
||||
"""
|
||||
try:
|
||||
return [d for d in os.listdir("levels") if isdir(join("levels", d))]
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
|
||||
def selection_menu(sounds, *options):
|
||||
"""Display level selection menu.
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of loaded sound effects
|
||||
*options: Variable number of menu options
|
||||
|
||||
Returns:
|
||||
str: Selected option or None if cancelled
|
||||
"""
|
||||
loop = True
|
||||
pygame.mixer.stop()
|
||||
i = 0
|
||||
j = -1
|
||||
|
||||
# Clear any pending events
|
||||
pygame.event.clear()
|
||||
|
||||
speak("Select an adventure")
|
||||
time.sleep(1.0)
|
||||
|
||||
while loop:
|
||||
if i != j:
|
||||
speak(options[i])
|
||||
j = i
|
||||
|
||||
pygame.event.pump()
|
||||
event = pygame.event.wait()
|
||||
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return None
|
||||
|
||||
if event.key == pygame.K_DOWN and i < len(options) - 1:
|
||||
i = i + 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
|
||||
if event.key == pygame.K_UP and i > 0:
|
||||
i = i - 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
|
||||
if event.key == pygame.K_HOME and i != 0:
|
||||
i = 0
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
|
||||
if event.key == pygame.K_END and i != len(options) - 1:
|
||||
i = len(options) - 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
|
||||
if event.key == pygame.K_RETURN:
|
||||
try:
|
||||
sounds['menu-select'].play()
|
||||
time.sleep(sounds['menu-select'].get_length())
|
||||
except:
|
||||
pass
|
||||
return options[i]
|
||||
elif event.type == pygame.QUIT:
|
||||
return None
|
||||
|
||||
pygame.event.pump()
|
||||
event = pygame.event.clear()
|
||||
time.sleep(0.001)
|
||||
|
||||
def select_game(sounds):
|
||||
"""Display game selection menu and return chosen game.
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of loaded sound effects
|
||||
|
||||
Returns:
|
||||
str: Selected game directory name or None if cancelled
|
||||
"""
|
||||
availableGames = get_available_games()
|
||||
|
||||
if not availableGames:
|
||||
speak("No games found in levels directory!")
|
||||
return None
|
||||
|
||||
# Convert directory names to display names (replace underscores with spaces)
|
||||
menuOptions = [game.replace("_", " ") for game in availableGames]
|
||||
|
||||
choice = selection_menu(sounds, *menuOptions)
|
||||
|
||||
if choice is None:
|
||||
return None
|
||||
|
||||
# Convert display name back to directory name if needed
|
||||
gameDir = choice.replace(" ", "_")
|
||||
if gameDir not in availableGames:
|
||||
gameDir = choice # Use original if conversion doesn't match
|
||||
|
||||
return gameDir
|
||||
|
||||
def get_level_path(gameDir, levelNum):
|
||||
"""Get full path to level JSON file.
|
||||
|
||||
Args:
|
||||
gameDir (str): Game directory name
|
||||
levelNum (int): Level number
|
||||
|
||||
Returns:
|
||||
str: Full path to level JSON file
|
||||
"""
|
||||
return os.path.join("levels", gameDir, f"{levelNum}.json")
|
@@ -5,6 +5,7 @@ from libstormgames import *
|
||||
from src.level import Level
|
||||
from src.object import Object
|
||||
from src.player import Player
|
||||
from src.game_selection import select_game, get_level_path
|
||||
|
||||
|
||||
class WickedQuest:
|
||||
@@ -15,24 +16,29 @@ class WickedQuest:
|
||||
self.gameStartTime = None
|
||||
self.lastThrowTime = 0
|
||||
self.throwDelay = 250
|
||||
self.player = None # Will be initialized when first level loads
|
||||
self.player = None
|
||||
self.currentGame = None
|
||||
|
||||
def load_level(self, levelNumber):
|
||||
"""Load a level from its JSON file."""
|
||||
levelFile = f"levels/{levelNumber}.json"
|
||||
levelFile = get_level_path(self.currentGame, levelNumber)
|
||||
pygame.event.pump()
|
||||
try:
|
||||
with open(levelFile, 'r') as f:
|
||||
levelData = json.load(f)
|
||||
|
||||
# Create player if this is the first level
|
||||
if self.player is None:
|
||||
self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"], self.sounds)
|
||||
self.player = Player(levelData["player_start"]["x"],
|
||||
levelData["player_start"]["y"],
|
||||
self.sounds)
|
||||
else:
|
||||
# Just update player position for new level
|
||||
self.player.xPos = levelData["player_start"]["x"]
|
||||
self.player.yPos = levelData["player_start"]["y"]
|
||||
|
||||
# Pass existing player to new level
|
||||
pygame.event.pump()
|
||||
self.currentLevel = Level(levelData, self.sounds, self.player)
|
||||
|
||||
# Announce level details
|
||||
@@ -49,6 +55,7 @@ class WickedQuest:
|
||||
keys = pygame.key.get_pressed()
|
||||
player = self.currentLevel.player
|
||||
currentTime = pygame.time.get_ticks()
|
||||
pygame.event.pump()
|
||||
|
||||
# Update running and ducking states
|
||||
if keys[pygame.K_s] and not player.isDucking:
|
||||
@@ -162,18 +169,19 @@ class WickedQuest:
|
||||
|
||||
while True:
|
||||
currentTime = pygame.time.get_ticks()
|
||||
pygame.event.pump()
|
||||
|
||||
# Game volume controls
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.KEYDOWN:
|
||||
# Check for Alt modifier
|
||||
mods = pygame.key.get_mods()
|
||||
alt_pressed = mods & pygame.KMOD_ALT
|
||||
altPressed = mods & pygame.KMOD_ALT
|
||||
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
return
|
||||
# Volume controls (require Alt)
|
||||
elif alt_pressed:
|
||||
elif altPressed:
|
||||
if event.key == pygame.K_PAGEUP:
|
||||
adjust_master_volume(0.1)
|
||||
elif event.key == pygame.K_PAGEDOWN:
|
||||
@@ -233,15 +241,18 @@ class WickedQuest:
|
||||
def run(self):
|
||||
"""Main game loop with menu system."""
|
||||
while True:
|
||||
choice = game_menu(self.sounds, "play", "instructions", "learn_sounds", "credits", "donate", "exit")
|
||||
choice = game_menu(self.sounds, "play", "instructions", "learn_sounds",
|
||||
"credits", "donate", "exit")
|
||||
|
||||
if choice == "exit":
|
||||
exit_game()
|
||||
elif choice == "play":
|
||||
self.player = None # Reset player for new game
|
||||
self.gameStartTime = pygame.time.get_ticks() # Set game start time here
|
||||
if self.load_level(1):
|
||||
self.game_loop()
|
||||
self.currentGame = select_game(self.sounds)
|
||||
if self.currentGame:
|
||||
self.player = None # Reset player for new game
|
||||
self.gameStartTime = pygame.time.get_ticks()
|
||||
if self.load_level(1):
|
||||
self.game_loop()
|
||||
elif choice == "learn_sounds":
|
||||
choice = learn_sounds(self.sounds)
|
||||
|
||||
|
Reference in New Issue
Block a user