From dd67eb5e1da5de0fdb5d78850b00ec30b4e18e9e Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Fri, 3 Oct 2025 01:46:28 -0400 Subject: [PATCH] Fix up some inconsistancies in the code levels should only be needed in one directory now, so update the hook to move levels into place on first run if needed. Warning, this version breaks existing save files. --- move_dirs_hook.py | 42 +++---- sounds/grave.ogg | 4 +- src/coffin.py | 28 ++--- src/enemy.py | 32 +++--- src/game_selection.py | 54 ++++----- src/grasping_hands.py | 6 +- src/item_types.py | 26 ++--- src/level.py | 116 +++++++++---------- src/player.py | 40 +++---- src/powerup.py | 58 +++++----- src/projectile.py | 10 +- src/save_manager.py | 152 ++++++++++++------------- src/survival_generator.py | 32 +++--- wicked_quest.py | 232 ++++++++++++++++++-------------------- 14 files changed, 410 insertions(+), 422 deletions(-) diff --git a/move_dirs_hook.py b/move_dirs_hook.py index b81a76d..1fe5739 100644 --- a/move_dirs_hook.py +++ b/move_dirs_hook.py @@ -5,53 +5,55 @@ import shutil # Runtime hook to move directories and files from _internal to parent directory if hasattr(sys, '_MEIPASS'): # We're running from a PyInstaller bundle - bundle_dir = os.path.dirname(sys.executable) - internal_dir = sys._MEIPASS + bundleDir = os.path.dirname(sys.executable) + internalDir = sys._MEIPASS # Directories to move from _internal to parent - dirs_to_move = ['sounds', 'libstormgames'] + # All these use the parent directory for easier user customization + dirsToMove = ['sounds', 'libstormgames', 'levels'] # Directories to copy (keep in both locations) - dirs_to_copy = ['levels'] + # None needed - everything now uses parent directory + dirsToCopy = [] # Files to move from _internal to parent - files_to_move = ['files', 'logo.png'] + filesToMove = ['files', 'logo.png'] # Move directories - for dir_name in dirs_to_move: - internal_path = os.path.join(internal_dir, dir_name) - target_path = os.path.join(bundle_dir, dir_name) + for dir_name in dirsToMove: + internalPath = os.path.join(internalDir, dir_name) + targetPath = os.path.join(bundleDir, dir_name) # Only move if source exists and target doesn't exist - if os.path.exists(internal_path) and not os.path.exists(target_path): + if os.path.exists(internalPath) and not os.path.exists(targetPath): try: - shutil.move(internal_path, target_path) + shutil.move(internalPath, targetPath) except Exception as e: # Silently fail if we can't move - game will still work from _internal pass # Copy directories (keep in both locations) - for dir_name in dirs_to_copy: - internal_path = os.path.join(internal_dir, dir_name) - target_path = os.path.join(bundle_dir, dir_name) + for dir_name in dirsToCopy: + internalPath = os.path.join(internalDir, dir_name) + targetPath = os.path.join(bundleDir, dir_name) # Only copy if source exists and target doesn't exist - if os.path.exists(internal_path) and not os.path.exists(target_path): + if os.path.exists(internalPath) and not os.path.exists(targetPath): try: - shutil.copytree(internal_path, target_path) + shutil.copytree(internalPath, targetPath) except Exception as e: # Silently fail if we can't copy - game will still work from _internal pass # Move files - for file_name in files_to_move: - internal_path = os.path.join(internal_dir, file_name) - target_path = os.path.join(bundle_dir, file_name) + for file_name in filesToMove: + internalPath = os.path.join(internalDir, file_name) + targetPath = os.path.join(bundleDir, file_name) # Only move if source exists and target doesn't exist - if os.path.exists(internal_path) and not os.path.exists(target_path): + if os.path.exists(internalPath) and not os.path.exists(targetPath): try: - shutil.move(internal_path, target_path) + shutil.move(internalPath, targetPath) except Exception as e: # Silently fail if we can't move - game will still work from _internal at least enough to exit. pass diff --git a/sounds/grave.ogg b/sounds/grave.ogg index cbaf9f9..62dea5d 100644 --- a/sounds/grave.ogg +++ b/sounds/grave.ogg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f63cf7cbb16dfb2782f2b0e4f57f67cc70b22be98820bbdf02aeae938aa57b04 -size 160157 +oid sha256:3f95061b4fb7a6b6acc4bd514123e4949b9cbccafdc069b874976f0b0fb1ec83 +size 183297 diff --git a/src/coffin.py b/src/coffin.py index de42ee8..1f58896 100644 --- a/src/coffin.py +++ b/src/coffin.py @@ -18,8 +18,8 @@ class CoffinObject(Object): self.sounds = sounds self.level = level self.isBroken = False - self.dropped_item = None - self.specified_item = item + self.droppedItem = None + self.specifiedItem = item def hit(self, player_pos): """Handle being hit by the player's weapon""" @@ -38,32 +38,32 @@ class CoffinObject(Object): self.isActive = False # Determine item to drop - if self.specified_item == "random": + if self.specifiedItem == "random": # Check if we're in survival mode (level_id 999) - is_survival_mode = hasattr(self.level, 'levelData') and self.level.levelData.get('level_id') == 999 - item_type = ItemProperties.get_random_item(survival_mode=is_survival_mode) + isSurvivalMode = hasattr(self.level, 'levelData') and self.level.levelData.get('level_id') == 999 + itemType = ItemProperties.get_random_item(survivalMode=isSurvivalMode) else: # Validate specified item - if ItemProperties.is_valid_item(self.specified_item): - item_type = self.specified_item + if ItemProperties.is_valid_item(self.specifiedItem): + itemType = self.specifiedItem else: # Fall back to random if invalid item specified # Check if we're in survival mode (level_id 999) - is_survival_mode = hasattr(self.level, 'levelData') and self.level.levelData.get('level_id') == 999 - item_type = ItemProperties.get_random_item(survival_mode=is_survival_mode) + isSurvivalMode = hasattr(self.level, 'levelData') and self.level.levelData.get('level_id') == 999 + itemType = ItemProperties.get_random_item(survivalMode=isSurvivalMode) # Create item 1-2 tiles away in random direction direction = random.choice([-1, 1]) - drop_distance = random.randint(1, 2) - drop_x = self.xPos + (direction * drop_distance) + dropDistance = random.randint(1, 2) + dropX = self.xPos + (direction * dropDistance) - self.dropped_item = PowerUp( - drop_x, self.yPos, item_type, self.sounds, direction, self.level.leftBoundary, self.level.rightBoundary + self.droppedItem = PowerUp( + dropX, self.yPos, itemType, self.sounds, direction, self.level.leftBoundary, self.level.rightBoundary ) # Apply sound override after creation (similar to how graves handle it) if hasattr(self, 'itemSoundOverride'): - self.dropped_item.soundName = self.itemSoundOverride + self.droppedItem.soundName = self.itemSoundOverride return True return False diff --git a/src/enemy.py b/src/enemy.py index a43a2fd..3ff77df 100644 --- a/src/enemy.py +++ b/src/enemy.py @@ -19,7 +19,7 @@ class Enemy(Object): self.level = level self.health = kwargs.get("health", 5) # Default 5 HP self.damage = kwargs.get("damage", 1) # Default 1 damage - self.attackRange = kwargs.get("attack_range", 1) # Default 1 tile range + self.attackRange = kwargs.get("attackRange", 1) # Default 1 tile range self.sounds = sounds # Store reference to game sounds # Movement and behavior properties @@ -32,25 +32,25 @@ class Enemy(Object): self._currentX = self.xRange[0] # Initialize current position # Add spawn configuration - self.canSpawn = kwargs.get("can_spawn", False) + self.canSpawn = kwargs.get("canSpawn", False) if self.canSpawn: - self.spawnCooldown = kwargs.get("spawn_cooldown", 2000) - self.spawnChance = kwargs.get("spawn_chance", 25) - self.spawnType = kwargs.get("spawn_type", "zombie") # Default to zombie for backward compatibility - self.spawnDistance = kwargs.get("spawn_distance", 5) + self.spawnCooldown = kwargs.get("spawnCooldown", 2000) + self.spawnChance = kwargs.get("spawnChance", 25) + self.spawnType = kwargs.get("spawnType", "zombie") # Default to zombie for backward compatibility + self.spawnDistance = kwargs.get("spawnDistance", 5) self.lastSpawnTime = 0 # Attack pattern configuration - self.attackPattern = kwargs.get("attack_pattern", {"type": "patrol"}) + self.attackPattern = kwargs.get("attackPattern", {"type": "patrol"}) self.turnThreshold = self.attackPattern.get("turn_threshold", 5) # Initialize vulnerability system - self.hasVulnerabilitySystem = kwargs.get("has_vulnerability", False) + self.hasVulnerabilitySystem = kwargs.get("hasVulnerability", False) if self.hasVulnerabilitySystem: self.isVulnerable = False # Start invulnerable self.vulnerabilityTimer = pygame.time.get_ticks() - self.vulnerabilityDuration = kwargs.get("vulnerability_duration", 2000) - self.invulnerabilityDuration = kwargs.get("invulnerability_duration", 5000) + self.vulnerabilityDuration = kwargs.get("vulnerabilityDuration", 2000) + self.invulnerabilityDuration = kwargs.get("invulnerabilityDuration", 5000) soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType self.channel = obj_play(self.sounds, soundName, self.level.player.xPos, self.xPos) else: @@ -63,7 +63,7 @@ class Enemy(Object): self.health = 1 # Easy to kill self.attackCooldown = 1500 # Slower attack rate elif enemyType == "spider": - speedMultiplier = kwargs.get("speed_multiplier", 2.0) + speedMultiplier = kwargs.get("speedMultiplier", 2.0) self.movementSpeed *= speedMultiplier # Spiders are faster self.attackPattern = {"type": "hunter"} # Spiders actively hunt the player self.turnThreshold = 3 # Spiders turn around quickly to chase player @@ -162,7 +162,7 @@ class Enemy(Object): # Set behavior based on game mode behavior = "hunter" if self.level.levelId == 999 else "patrol" - turn_rate = 2 if self.level.levelId == 999 else 8 # Faster turn rate for survival + turnRate = 2 if self.level.levelId == 999 else 8 # Faster turn rate for survival # Create new enemy of specified type spawned = Enemy( @@ -173,9 +173,9 @@ class Enemy(Object): self.level, health=4, # Default health for spawned enemies damage=2, # Default damage for spawned enemies - attack_range=1, # Default range for spawned enemies - attack_pattern={"type": behavior}, - turn_rate=turn_rate, + attackRange=1, # Default range for spawned enemies + attackPattern={"type": behavior}, + turnRate=turnRate, ) # Add to level's enemies @@ -273,7 +273,7 @@ class Enemy(Object): droppedItem = PowerUp( dropX, self.yPos, itemType, self.sounds, direction, self.level.leftBoundary, self.level.rightBoundary ) - self.level.bouncing_items.append(droppedItem) + self.level.bouncingItems.append(droppedItem) # Update stats self.level.player.stats.update_stat("Enemies killed", 1) diff --git a/src/game_selection.py b/src/game_selection.py index 89661ae..b0c79c5 100644 --- a/src/game_selection.py +++ b/src/game_selection.py @@ -15,7 +15,9 @@ def get_levels_base_path(): """ if hasattr(sys, "_MEIPASS"): # Running as PyInstaller executable - return sys._MEIPASS + # Use parent directory (where executable is) instead of _internal + # This allows users to add new level packs without recompiling + return os.path.dirname(sys.executable) else: # Running as script return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -28,9 +30,9 @@ def get_available_games(): list: List of game directory names """ try: - base_path = get_levels_base_path() - levels_path = os.path.join(base_path, "levels") - return [d for d in os.listdir(levels_path) if isdir(join(levels_path, d)) and not d.endswith(".md")] + basePath = get_levels_base_path() + levelsPath = os.path.join(basePath, "levels") + return [d for d in os.listdir(levelsPath) if isdir(join(levelsPath, d)) and not d.endswith(".md")] except FileNotFoundError: return [] @@ -57,57 +59,57 @@ def select_game(sounds): Returns: str: Selected game directory name or None if cancelled """ - available_games = get_available_games() + availableGames = get_available_games() - if not available_games: + if not availableGames: speak("No games found in levels directory!") return None # Convert directory names to display names (replace underscores with spaces) - menu_options = [game.replace("_", " ") for game in available_games] + menuOptions = [game.replace("_", " ") for game in availableGames] - choice = selection_menu(sounds, *menu_options) + choice = selection_menu(sounds, *menuOptions) if choice is None: return None # Convert display name back to directory name if needed - game_dir = choice.replace(" ", "_") - if game_dir not in available_games: - game_dir = choice # Use original if conversion doesn't match + gameDir = choice.replace(" ", "_") + if gameDir not in availableGames: + gameDir = choice # Use original if conversion doesn't match - return game_dir + return gameDir -def get_level_path(game_dir, level_num): +def get_level_path(gameDir, levelNum): """Get full path to level JSON file. Args: - game_dir (str): Game directory name - level_num (int): Level number + gameDir (str): Game directory name + levelNum (int): Level number Returns: str: Full path to level JSON file """ - if game_dir is None: - raise ValueError("game_dir cannot be None") + if gameDir is None: + raise ValueError("gameDir cannot be None") - base_path = get_levels_base_path() - level_path = os.path.join(base_path, "levels", game_dir, f"{level_num}.json") - return level_path + basePath = get_levels_base_path() + levelPath = os.path.join(basePath, "levels", gameDir, f"{levelNum}.json") + return levelPath -def get_game_dir_path(game_dir): +def get_game_dir_path(gameDir): """Get full path to game directory for end.ogg and other game files. Args: - game_dir (str): Game directory name + gameDir (str): Game directory name Returns: str: Full path to game directory """ - if game_dir is None: - raise ValueError("game_dir cannot be None") + if gameDir is None: + raise ValueError("gameDir cannot be None") - base_path = get_levels_base_path() - return os.path.join(base_path, "levels", game_dir) + basePath = get_levels_base_path() + return os.path.join(basePath, "levels", gameDir) diff --git a/src/grasping_hands.py b/src/grasping_hands.py index 3eb72a0..16ca986 100644 --- a/src/grasping_hands.py +++ b/src/grasping_hands.py @@ -8,7 +8,7 @@ from src.object import Object class GraspingHands(Object): """A hazard where the ground crumbles beneath the player as undead hands reach up.""" - def __init__(self, xRange, y, sounds, delay=1000, crumble_speed=0.065): + def __init__(self, xRange, y, sounds, delay=1000, crumbleSpeed=0.065): super().__init__( xRange, y, @@ -19,7 +19,7 @@ class GraspingHands(Object): ) self.sounds = sounds self.delay = delay # Delay in milliseconds before ground starts crumbling - self.crumble_speed = crumble_speed # How fast the crumbling catches up (tiles per frame) + self.crumbleSpeed = crumbleSpeed # How fast the crumbling catches up (tiles per frame) # Sound prefix for different sound effects (can be overridden) self.soundPrefix = "grasping_hands" @@ -95,7 +95,7 @@ class GraspingHands(Object): # If triggered and delay has passed, start crumbling if self.isTriggered and currentTime - self.triggerTime >= self.delay: # Update crumble position based on direction - self.crumblePosition += self.crumble_speed * self.crumbleDirection + self.crumblePosition += self.crumbleSpeed * self.crumbleDirection # Manage the looping positional audio for the crumbling ground if self.crumbleChannel is None or not self.crumbleChannel.get_busy(): diff --git a/src/item_types.py b/src/item_types.py index 9af0fd4..b032d5c 100644 --- a/src/item_types.py +++ b/src/item_types.py @@ -45,28 +45,28 @@ class ItemProperties: } @staticmethod - def get_sound_name(item_type): + def get_sound_name(itemType): """Convert enum to sound/asset name""" - return ItemProperties.ALL_ITEMS.get(item_type) + return ItemProperties.ALL_ITEMS.get(itemType) @staticmethod - def get_random_item(survival_mode=False): + def get_random_item(survivalMode=False): """Get a random item from eligible items""" - if survival_mode: - item_type = random.choice(list(ItemProperties.SURVIVAL_MODE_ELIGIBLE.keys())) + if survivalMode: + itemType = random.choice(list(ItemProperties.SURVIVAL_MODE_ELIGIBLE.keys())) else: - item_type = random.choice(list(ItemProperties.STORY_MODE_ELIGIBLE.keys())) - return ItemProperties.get_sound_name(item_type) + itemType = random.choice(list(ItemProperties.STORY_MODE_ELIGIBLE.keys())) + return ItemProperties.get_sound_name(itemType) @staticmethod - def is_valid_item(item_name): + def is_valid_item(itemName): """Check if an item name is valid""" - return item_name in [v for v in ItemProperties.ALL_ITEMS.values()] + return itemName in [v for v in ItemProperties.ALL_ITEMS.values()] @staticmethod - def get_item_type(item_name): + def get_item_type(itemName): """Get ItemType enum from string name""" - for item_type, name in ItemProperties.ALL_ITEMS.items(): - if name == item_name: - return item_type + for itemType, name in ItemProperties.ALL_ITEMS.items(): + if name == itemName: + return itemType return None diff --git a/src/level.py b/src/level.py index 5c9f208..f85f7e2 100644 --- a/src/level.py +++ b/src/level.py @@ -22,12 +22,12 @@ class Level: self.levelPackName = levelPackName self.objects = [] self.enemies = [] - self.bouncing_items = [] + self.bouncingItems = [] self.projectiles = [] # Track active projectiles self.player = player self.lastWarningTime = 0 self.warningInterval = int(self.sounds["edge"].get_length() * 1000) # Convert seconds to milliseconds - self.weapon_hit_channel = None + self.weaponHitChannel = None self.leftBoundary = levelData["boundaries"]["left"] self.rightBoundary = levelData["boundaries"]["right"] @@ -137,7 +137,7 @@ class Level: obj["y"], self.sounds, delay=obj.get("delay", 1000), - crumble_speed=obj.get("crumble_speed", 0.03), + crumbleSpeed=obj.get("crumble_speed", 0.03), ) # Apply sound overrides if specified if "sound_overrides" in obj: @@ -146,25 +146,25 @@ class Level: # Check if this is a grave elif obj.get("type") == "grave": # Handle item spawning with chance (for survival mode) - grave_item = None + graveItem = None if "item_spawn_chance" in obj: # Survival mode: spawn item based on chance (random items already resolved) - spawn_chance = obj.get("item_spawn_chance", 0) - if random.randint(1, 100) <= spawn_chance: - grave_item = obj.get("item") + spawnChance = obj.get("item_spawn_chance", 0) + if random.randint(1, 100) <= spawnChance: + graveItem = obj.get("item") else: # Story mode: use item as specified - grave_item = obj.get("item", None) - if grave_item == "random": + graveItem = obj.get("item", None) + if graveItem == "random": # Check if we're in survival mode (level_id 999) - is_survival_mode = self.levelData.get('level_id') == 999 - grave_item = ItemProperties.get_random_item(survival_mode=is_survival_mode) + isSurvivalMode = self.levelData.get('level_id') == 999 + graveItem = ItemProperties.get_random_item(survivalMode=isSurvivalMode) grave = GraveObject( xPos[0], obj["y"], self.sounds, - item=grave_item, + item=graveItem, zombieSpawnChance=obj.get("zombie_spawn_chance", 0), ) # Apply sound overrides if specified @@ -232,18 +232,18 @@ class Level: self, # Pass level reference health=obj.get("health", 5), damage=obj.get("damage", 1), - attack_range=obj.get("attack_range", 1), - movement_range=obj.get("movement_range", 5), - attack_pattern=obj.get("attack_pattern", {"type": "patrol"}), - can_spawn=obj.get("can_spawn", False), - spawn_type=obj.get("spawn_type", "zombie"), - spawn_cooldown=obj.get("spawn_cooldown", 2000), - spawn_chance=obj.get("spawn_chance", 25), - spawn_distance=obj.get("spawn_distance", 5), - has_vulnerability=obj.get("has_vulnerability", False), - is_vulnerable=obj.get("is_vulnerable", False), - vulnerability_duration=obj.get("vulnerability_duration", 1000), - invulnerability_duration=obj.get("invulnerability_duration", 5000), + attackRange=obj.get("attack_range", 1), + movementRange=obj.get("movement_range", 5), + attackPattern=obj.get("attack_pattern", {"type": "patrol"}), + canSpawn=obj.get("can_spawn", False), + spawnType=obj.get("spawn_type", "zombie"), + spawnCooldown=obj.get("spawn_cooldown", 2000), + spawnChance=obj.get("spawn_chance", 25), + spawnDistance=obj.get("spawn_distance", 5), + hasVulnerability=obj.get("has_vulnerability", False), + isVulnerable=obj.get("is_vulnerable", False), + vulnerabilityDuration=obj.get("vulnerability_duration", 1000), + invulnerabilityDuration=obj.get("invulnerability_duration", 5000), ) self.enemies.append(enemy) else: @@ -286,7 +286,7 @@ class Level: if roll <= obj.zombieSpawnChance: # Set behavior based on game mode behavior = "hunter" if self.levelId == 999 else "patrol" - turn_rate = 2 if self.levelId == 999 else 8 # Faster turn rate for survival + turnRate = 2 if self.levelId == 999 else 8 # Faster turn rate for survival zombie = Enemy( [obj.xPos, obj.xPos], @@ -296,9 +296,9 @@ class Level: self, # Pass the level reference health=3, damage=10, - attack_range=1, - attack_pattern={"type": behavior}, - turn_rate=turn_rate, + attackRange=1, + attackPattern={"type": behavior}, + turnRate=turnRate, ) self.enemies.append(zombie) speak("A zombie emerges from the grave!") @@ -354,9 +354,9 @@ class Level: self.objects = [obj for obj in self.objects if obj.isActive] # Update bouncing items - for item in self.bouncing_items[:]: # Copy list to allow removal + for item in self.bouncingItems[:]: # Copy list to allow removal if not item.update(currentTime, self.player.xPos): - self.bouncing_items.remove(item) + self.bouncingItems.remove(item) if not item.isActive: speak(f"{item.soundName} got away!") continue @@ -367,7 +367,7 @@ class Level: item.apply_effect(self.player, self) self.levelScore += 1000 # All items collected points awarded item.isActive = False - self.bouncing_items.remove(item) + self.bouncingItems.remove(item) def handle_combat(self, currentTime): """Handle combat interactions between player and enemies""" @@ -394,7 +394,7 @@ class Level: ): # Must be jumping to hit floating coffins if obj.hit(self.player.xPos): - self.bouncing_items.append(obj.dropped_item) + self.bouncingItems.append(obj.droppedItem) def spawn_spider(self, xPos, yPos): """Spawn a spider at the given position""" @@ -406,8 +406,8 @@ class Level: self, health=8, damage=8, - attack_range=1, - speed_multiplier=2.0, + attackRange=1, + speedMultiplier=2.0, ) self.enemies.append(spider) @@ -417,20 +417,20 @@ class Level: if enemyStats: health = enemyStats.get("health", 8) damage = enemyStats.get("damage", 2) - speed_multiplier = enemyStats.get("speed_multiplier", 1.0) - attack_range = enemyStats.get("attack_range", 1) + speedMultiplier = enemyStats.get("speed_multiplier", 1.0) + attackRange = enemyStats.get("attack_range", 1) else: # Default stats for common enemy types (backward compatibility) if enemyType == "spider": - health, damage, speed_multiplier = 8, 8, 2.0 + health, damage, speedMultiplier = 8, 8, 2.0 elif enemyType == "elf": - health, damage, speed_multiplier = 2, 1, 1.0 + health, damage, speedMultiplier = 2, 1, 1.0 elif enemyType == "witch": - health, damage, speed_multiplier = 6, 2, 1.0 + health, damage, speedMultiplier = 6, 2, 1.0 else: # For any other enemy type, use reasonable defaults - health, damage, speed_multiplier = 6, 2, 1.0 - attack_range = 1 + health, damage, speedMultiplier = 6, 2, 1.0 + attackRange = 1 enemy = Enemy( [xPos - 5, xPos + 5], # Give enemy a patrol range @@ -440,9 +440,9 @@ class Level: self, health=health, damage=damage, - attack_range=attack_range, - speed_multiplier=speed_multiplier, - attack_pattern={"type": "hunter"}, # Hunt like original spiders + attackRange=attackRange, + speedMultiplier=speedMultiplier, + attackPattern={"type": "hunter"}, # Hunt like original spiders ) self.enemies.append(enemy) @@ -550,16 +550,16 @@ class Level: # Handle graves and other hazards if obj.isHazard and not self.player.isJumping: if isinstance(obj, GraveObject): - can_collect = obj.collect_grave_item(self.player) and not self.player.diedThisFrame - can_fill = obj.can_fill_grave(self.player) + canCollect = obj.collect_grave_item(self.player) and not self.player.diedThisFrame + canFill = obj.can_fill_grave(self.player) - if can_collect: + if canCollect: # Successfully collected item while ducking with shovel # Check for item sound override - item_sound = obj.graveItem + itemSound = obj.graveItem if hasattr(obj, 'itemSoundOverride'): - item_sound = obj.itemSoundOverride - play_sound(self.sounds[f"get_{item_sound}"]) + itemSound = obj.itemSoundOverride + play_sound(self.sounds[f"get_{itemSound}"]) play_sound(self.sounds.get("fill_in_grave", "shovel_dig")) # Also play fill sound self.player.stats.update_stat("Items collected", 1) # Create PowerUp to handle the item effect @@ -575,7 +575,7 @@ class Level: obj.channel = None obj.isActive = False # Mark the grave as inactive after collection continue - elif can_fill and obj.fill_grave(self.player): + elif canFill and obj.fill_grave(self.player): # Successfully filled empty grave with shovel play_sound( self.sounds.get("fill_in_grave", "shovel_dig") @@ -643,12 +643,12 @@ class Level: def throw_projectile(self): """Have player throw a projectile""" - proj_info = self.player.throw_projectile() - if proj_info is None: + projInfo = self.player.throw_projectile() + if projInfo is None: speak("No jack o'lanterns to throw!") return - self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"])) + self.projectiles.append(Projectile(projInfo["type"], projInfo["start_x"], projInfo["direction"])) # Play throw sound play_sound(self.sounds["throw_jack_o_lantern"]) @@ -663,11 +663,11 @@ class Level: # Create projectile with weapon-specific properties # Use a generic projectile type for weapon projectiles - projectile_type = f"weapon_{weapon.name}" - start_x = player.xPos + (direction * 1) # Start 1 tile away from player + projectileType = f"weapon_{weapon.name}" + startX = player.xPos + (direction * 1) # Start 1 tile away from player # Create projectile with custom speed and damage from weapon - projectile = Projectile(projectile_type, start_x, direction) + projectile = Projectile(projectileType, startX, direction) # Override projectile properties with weapon stats projectile.damage = weapon.damage @@ -822,7 +822,7 @@ class Level: obj.itemSoundOverride = soundOverrides["item"] # Handle item sound overrides for coffins - if hasattr(obj, 'specified_item') and obj.specified_item and obj.specified_item != "random" and "item" in soundOverrides: + if hasattr(obj, 'specifiedItem') and obj.specifiedItem and obj.specifiedItem != "random" and "item" in soundOverrides: # Store the override for later use when coffin is broken if not hasattr(obj, 'itemSoundOverride'): obj.itemSoundOverride = soundOverrides["item"] diff --git a/src/player.py b/src/player.py index 328f144..f446c54 100644 --- a/src/player.py +++ b/src/player.py @@ -45,7 +45,7 @@ class Player: self.collectedItems = [] self._coins = 0 # Regular bone dust for extra lives self._saveBoneDust = 0 # Separate bone dust counter for saves - self._jack_o_lantern_count = 0 + self._jackOLanternCount = 0 self.shinBoneCount = 0 # Combat related attributes @@ -115,7 +115,7 @@ class Player: # Check invincibility status if self.isInvincible: - remaining_time = ( + remainingTime = ( self.invincibilityStartTime + self.invincibilityDuration - currentTime ) / 1000 # Convert to seconds @@ -123,10 +123,10 @@ class Player: if not hasattr(self, "_last_countdown"): self._last_countdown = 4 # Start counting from 4 to catch 3,2,1 - current_second = int(remaining_time) - if current_second < self._last_countdown and current_second <= 3 and current_second > 0: + currentSecond = int(remainingTime) + if currentSecond < self._last_countdown and currentSecond <= 3 and currentSecond > 0: play_sound(self.sounds["end_of_invincibility_warning"]) - self._last_countdown = current_second + self._last_countdown = currentSecond # Check if invincibility has expired if currentTime - self.invincibilityStartTime >= self.invincibilityDuration: @@ -147,11 +147,11 @@ class Player: def get_jack_o_lanterns(self): """Get number of jack o'lanterns""" - return self._jack_o_lantern_count + return self._jackOLanternCount def add_jack_o_lantern(self): """Add a jack o'lantern""" - self._jack_o_lantern_count += 1 + self._jackOLanternCount += 1 def add_guts(self): """Apply guts, increase max_health by 2 if less than 20 else restore health""" @@ -165,7 +165,7 @@ class Player: if self.get_jack_o_lanterns() <= 0: return None - self._jack_o_lantern_count -= 1 + self._jackOLanternCount -= 1 return {"type": "jack_o_lantern", "start_x": self.xPos, "direction": 1 if self.facingRight else -1} def get_step_distance(self): @@ -220,19 +220,19 @@ class Player: def set_health(self, value): """Set health and handle death if needed.""" - old_health = self._health + oldHealth = self._health # Oops, allow healing while invincible. - if self.isInvincible and value < old_health: + if self.isInvincible and value < oldHealth: return # Check for god mode (prevents all damage) - if hasattr(self, '_godMode') and self._godMode and value < old_health: + if hasattr(self, '_godMode') and self._godMode and value < oldHealth: return self._health = max(0, value) # Health can't go below 0 - if self._health == 0 and old_health > 0: + if self._health == 0 and oldHealth > 0: self._lives -= 1 # Mark that player died this frame to prevent revival self.diedThisFrame = True @@ -379,7 +379,7 @@ class Player: elif ammoType == "hand_of_glory": return self.collectedItems.count("hand_of_glory") >= cost elif ammoType == "jack_o_lantern": - return self._jack_o_lantern_count >= cost + return self._jackOLanternCount >= cost else: # Check for any other item type in collectedItems return self.collectedItems.count(ammoType) >= cost @@ -398,7 +398,7 @@ class Player: for _ in range(min(cost, self.collectedItems.count("hand_of_glory"))): self.collectedItems.remove("hand_of_glory") elif ammoType == "jack_o_lantern": - self._jack_o_lantern_count = max(0, self._jack_o_lantern_count - cost) + self._jackOLanternCount = max(0, self._jackOLanternCount - cost) else: # Handle any other item type for _ in range(min(cost, self.collectedItems.count(ammoType))): @@ -409,18 +409,18 @@ class Player: # Check for themed mappings in weapon overrides if hasattr(self, 'weaponOverrides') and self.weaponOverrides: # Define themed mappings (this could be extended or made configurable) - themed_mappings = { + themedMappings = { "shin_bone": "candy_cane", # Christmas theme example "guts": "reindeer_guts" } # Check if there's a themed equivalent and if the override exists - if ammoType in themed_mappings: - themed_name = themed_mappings[ammoType] + if ammoType in themedMappings: + themedName = themedMappings[ammoType] # If the themed item exists in overrides, use the themed name - for override_key, override_data in self.weaponOverrides.items(): - if isinstance(override_data, dict) and themed_name in str(override_data): - return themed_name.replace("_", " ") + for overrideKey, overrideData in self.weaponOverrides.items(): + if isinstance(overrideData, dict) and themedName in str(overrideData): + return themedName.replace("_", " ") # Return default name return ammoType.replace("_", " ") diff --git a/src/powerup.py b/src/powerup.py index 9067a2d..ad9b0b0 100644 --- a/src/powerup.py +++ b/src/powerup.py @@ -7,43 +7,43 @@ from src.weapon import Weapon class PowerUp(Object): - def __init__(self, x, y, item_type, sounds, direction, left_boundary=1, right_boundary=100): - super().__init__(x, y, item_type, isStatic=False, isCollectible=True, isHazard=False) + def __init__(self, x, y, itemType, sounds, direction, leftBoundary=1, rightBoundary=100): + super().__init__(x, y, itemType, isStatic=False, isCollectible=True, isHazard=False) self.sounds = sounds self.direction = direction self.speed = 0.049 # Base movement speed - self.item_type = item_type + self.itemType = itemType self.channel = None self._currentX = x # Initialize the current x position - self.left_boundary = left_boundary - self.right_boundary = right_boundary + self.leftBoundary = leftBoundary + self.rightBoundary = rightBoundary - def update(self, current_time, player_pos): + def update(self, currentTime, playerPos): """Update item position""" if not self.isActive: return False # Update position - new_x = self._currentX + self.direction * self.speed + newX = self._currentX + self.direction * self.speed # Check boundaries and bounce if needed - if new_x < self.left_boundary: - self._currentX = self.left_boundary + if newX < self.leftBoundary: + self._currentX = self.leftBoundary self.direction = 1 # Start moving right - elif new_x > self.right_boundary: - self._currentX = self.right_boundary + elif newX > self.rightBoundary: + self._currentX = self.rightBoundary self.direction = -1 # Start moving left else: - self._currentX = new_x + self._currentX = newX # Update positional audio if self.channel is None or not self.channel.get_busy(): - self.channel = obj_play(self.sounds, "item_bounce", player_pos, self._currentX) + self.channel = obj_play(self.sounds, "item_bounce", playerPos, self._currentX) else: - self.channel = obj_update(self.channel, player_pos, self._currentX) + self.channel = obj_update(self.channel, playerPos, self._currentX) # Check if item is too far from player (12 tiles) - if abs(self._currentX - player_pos) > 12: + if abs(self._currentX - playerPos) > 12: self.isActive = False if self.channel: self.channel.stop() @@ -55,22 +55,22 @@ class PowerUp(Object): def apply_effect(self, player, level=None): """Apply the item's effect when collected""" # Map themed items to their original equivalents for game logic - original_item_type = self._get_original_item_type(self.item_type) + originalItemType = self._get_original_item_type(self.itemType) - if original_item_type == "hand_of_glory": + if originalItemType == "hand_of_glory": player.start_invincibility() self.check_for_custom_weapons(player) - elif original_item_type == "cauldron": + elif originalItemType == "cauldron": player.restore_health() - elif original_item_type == "guts": + elif originalItemType == "guts": player.add_guts() - player.collectedItems.append(original_item_type) + player.collectedItems.append(originalItemType) self.check_for_nunchucks(player) self.check_for_custom_weapons(player) - elif original_item_type == "jack_o_lantern": + elif originalItemType == "jack_o_lantern": player.add_jack_o_lantern() self.check_for_custom_weapons(player) - elif original_item_type == "extra_life": + elif originalItemType == "extra_life": # Don't give extra lives in survival mode if level and level.levelId == 999: # In survival mode, give bonus score instead @@ -79,7 +79,7 @@ class PowerUp(Object): play_sound(self.sounds.get("survivor_bonus", "get_extra_life")) # Use survivor_bonus sound if available else: player.extra_life() - elif original_item_type == "shin_bone": # Add shin bone handling + elif originalItemType == "shin_bone": # Add shin bone handling player.shinBoneCount += 1 player._coins += 5 player.add_save_bone_dust(5) # Add to save bone dust counter too @@ -106,11 +106,11 @@ class PowerUp(Object): self.check_for_nunchucks(player) self.check_for_custom_weapons(player) - elif original_item_type == "witch_broom": + elif originalItemType == "witch_broom": broomWeapon = Weapon.create_witch_broom() player.add_weapon(broomWeapon) player.equip_weapon(broomWeapon) - elif original_item_type == "spiderweb": + elif originalItemType == "spiderweb": # Bounce player back (happens even if invincible) player.xPos -= 3 if player.xPos > self.xPos else -3 @@ -193,7 +193,7 @@ class PowerUp(Object): canCraft = False break elif itemType == "jack_o_lantern": - if player._jack_o_lantern_count < neededCount: + if player._jackOLanternCount < neededCount: canCraft = False break else: @@ -225,10 +225,10 @@ class PowerUp(Object): player.stats.update_stat("Items collected", 1) - def _get_original_item_type(self, item_type): + def _get_original_item_type(self, itemType): """Map themed item names to their original equivalents for game logic.""" # Define themed equivalents that should behave like original items - themed_mappings = { + themedMappings = { # Christmas theme "candy_cane": "shin_bone", "reindeer_guts": "guts", @@ -237,4 +237,4 @@ class PowerUp(Object): # "frozen_heart": "guts", } - return themed_mappings.get(item_type, item_type) + return themedMappings.get(itemType, itemType) diff --git a/src/projectile.py b/src/projectile.py index 7907a2a..b0a04cf 100644 --- a/src/projectile.py +++ b/src/projectile.py @@ -2,15 +2,15 @@ class Projectile: - def __init__(self, projectile_type, start_x, direction): - self.type = projectile_type - self.x = start_x + def __init__(self, projectileType, startX, direction): + self.type = projectileType + self.x = startX self.direction = direction self.speed = 0.2 # Projectiles move faster than player self.isActive = True self.damage = 5 # All projectiles do same damage for now self.range = 12 # Maximum travel distance in tiles - self.start_x = start_x + self.startX = startX def update(self): """Update projectile position and check if it should still exist""" @@ -20,7 +20,7 @@ class Projectile: self.x += self.direction * self.speed # Check if projectile has gone too far - if abs(self.x - self.start_x) > self.range: + if abs(self.x - self.startX) > self.range: self.isActive = False return False diff --git a/src/save_manager.py b/src/save_manager.py index 4028f7b..15ad286 100644 --- a/src/save_manager.py +++ b/src/save_manager.py @@ -11,32 +11,32 @@ class SaveManager: def __init__(self): """Initialize save manager with XDG-compliant save directory""" # Use XDG_CONFIG_HOME or default to ~/.config - config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) - self.save_dir = Path(config_home) / "storm-games" / "wicked-quest" - self.save_dir.mkdir(parents=True, exist_ok=True) - self.max_saves = 10 + configHome = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + self.saveDir = Path(configHome) / "storm-games" / "wicked-quest" + self.saveDir.mkdir(parents=True, exist_ok=True) + self.maxSaves = 10 - def create_save(self, player, current_level, game_start_time, current_game, bypass_cost=False): + def create_save(self, player, currentLevel, gameStartTime, currentGame, bypassCost=False): """Create a save file with current game state""" - if not bypass_cost: + if not bypassCost: if not player.can_save(): return False, "Not enough bone dust to save (need 200)" # Validate required parameters - if current_game is None: + if currentGame is None: return False, "No game selected to save" - if current_level is None: + if currentLevel is None: return False, "No current level to save" # Spend the bone dust (only if not bypassing cost) - if not bypass_cost: + if not bypassCost: if not player.spend_save_bone_dust(200): return False, "Failed to spend bone dust" # Create save data - save_data = { - "player_state": { + saveData = { + "playerState": { "xPos": player.xPos, "yPos": player.yPos, "health": player._health, @@ -44,7 +44,7 @@ class SaveManager: "lives": player._lives, "coins": player._coins, "saveBoneDust": player._saveBoneDust, - "jackOLanternCount": player._jack_o_lantern_count, + "jackOLanternCount": player._jackOLanternCount, "shinBoneCount": player.shinBoneCount, "inventory": player.inventory, "collectedItems": player.collectedItems, @@ -56,9 +56,9 @@ class SaveManager: "scoreboard": self._serialize_scoreboard(player.scoreboard), }, "game_state": { - "currentLevel": current_level, - "currentGame": current_game, - "gameStartTime": game_start_time, + "currentLevel": currentLevel, + "currentGame": currentGame, + "gameStartTime": gameStartTime, "saveTime": datetime.now(), }, "version": "1.0", @@ -67,21 +67,21 @@ class SaveManager: # Generate filename with timestamp timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") filename = f"save_{timestamp}.pickle" - filepath = self.save_dir / filename + filepath = self.saveDir / filename try: # Write to temporary file first, then rename for atomic operation - temp_filepath = filepath.with_suffix(".tmp") + tempFilepath = filepath.with_suffix(".tmp") - with open(temp_filepath, "wb") as f: - pickle.dump(save_data, f) + with open(tempFilepath, "wb") as f: + pickle.dump(saveData, f) f.flush() # Ensure data is written to disk os.fsync(f.fileno()) # Force write to disk # Atomic rename (replaces old file if it exists) - temp_filepath.rename(filepath) + tempFilepath.rename(filepath) - # Clean up old saves if we exceed max_saves + # Clean up old saves if we exceed maxSaves self._cleanup_old_saves() return True, f"Game saved to {filename}" @@ -205,38 +205,38 @@ class SaveManager: scoreboard.highScores = scoreboard_data["highScores"] return scoreboard - def get_save_files(self): + def get_saveFiles(self): """Get list of save files with metadata""" - save_files = [] - pattern = str(self.save_dir / "save_*.pickle") + saveFiles = [] + pattern = str(self.saveDir / "save_*.pickle") for filepath in glob.glob(pattern): try: with open(filepath, "rb") as f: - save_data = pickle.load(f) + saveData = pickle.load(f) # Validate save data structure - if not self._validate_save_data(save_data): + if not self._validate_saveData(saveData): print(f"Invalid save file structure: {filepath}") continue # Extract save info - save_time = save_data["game_state"]["saveTime"] - level = save_data["game_state"]["currentLevel"] - game_name = save_data["game_state"]["currentGame"] + saveTime = saveData["game_state"]["saveTime"] + level = saveData["game_state"]["currentLevel"] + gameName = saveData["game_state"]["currentGame"] # Format display name - formatted_time = save_time.strftime("%B %d %I:%M%p") - display_name = f"{formatted_time} {game_name} Level {level}" + formattedTime = saveTime.strftime("%B %d %I:%M%p") + displayName = f"{formattedTime} {gameName} Level {level}" - save_files.append( + saveFiles.append( { "filepath": filepath, - "display_name": display_name, - "save_time": save_time, + "displayName": displayName, + "saveTime": saveTime, "level": level, - "game_name": game_name, - "save_data": save_data, + "gameName": gameName, + "saveData": saveData, } ) except (pickle.PickleError, EOFError, OSError) as e: @@ -253,94 +253,94 @@ class SaveManager: continue # Sort by save time (newest first) - save_files.sort(key=lambda x: x["save_time"], reverse=True) - return save_files + saveFiles.sort(key=lambda x: x["saveTime"], reverse=True) + return saveFiles def load_save(self, filepath): """Load game state from save file""" try: with open(filepath, "rb") as f: - save_data = pickle.load(f) - return True, save_data + saveData = pickle.load(f) + return True, saveData except Exception as e: return False, f"Failed to load save: {str(e)}" - def restore_player_state(self, player, save_data): + def restore_playerState(self, player, saveData): """Restore player state from save data""" - player_state = save_data["player_state"] + playerState = saveData["playerState"] # Restore basic attributes - player.xPos = player_state["xPos"] - player.yPos = player_state["yPos"] - player._health = player_state["health"] - player._maxHealth = player_state["maxHealth"] - player._lives = player_state["lives"] - player._coins = player_state["coins"] - player._saveBoneDust = player_state["saveBoneDust"] - player._jack_o_lantern_count = player_state["jackOLanternCount"] - player.shinBoneCount = player_state["shinBoneCount"] - player.inventory = player_state["inventory"] - player.collectedItems = player_state["collectedItems"] + player.xPos = playerState["xPos"] + player.yPos = playerState["yPos"] + player._health = playerState["health"] + player._maxHealth = playerState["maxHealth"] + player._lives = playerState["lives"] + player._coins = playerState["coins"] + player._saveBoneDust = playerState["saveBoneDust"] + player._jackOLanternCount = playerState["jackOLanternCount"] + player.shinBoneCount = playerState["shinBoneCount"] + player.inventory = playerState["inventory"] + player.collectedItems = playerState["collectedItems"] # Restore weapons - player.weapons = self._deserialize_weapons(player_state["weapons"]) + player.weapons = self._deserialize_weapons(playerState["weapons"]) # Restore custom weapon tracking data (for backward compatibility, use get with defaults) - player.craftedCustomWeapons = set(player_state.get("craftedCustomWeapons", [])) - player.customWeapons = player_state.get("customWeapons", []) + player.craftedCustomWeapons = set(playerState.get("craftedCustomWeapons", [])) + player.customWeapons = playerState.get("customWeapons", []) # Restore current weapon - current_weapon_name = player_state.get("currentWeaponName") - if current_weapon_name: + currentWeaponName = playerState.get("currentWeaponName") + if currentWeaponName: for weapon in player.weapons: - if weapon.name == current_weapon_name: + if weapon.name == currentWeaponName: player.currentWeapon = weapon break # Restore stats - if "stats" in player_state: - player.stats = self._deserialize_stats(player_state["stats"]) + if "stats" in playerState: + player.stats = self._deserialize_stats(playerState["stats"]) else: from src.stat_tracker import StatTracker player.stats = StatTracker() # Restore scoreboard - if "scoreboard" in player_state: - player.scoreboard = self._deserialize_scoreboard(player_state["scoreboard"]) + if "scoreboard" in playerState: + player.scoreboard = self._deserialize_scoreboard(playerState["scoreboard"]) else: from libstormgames import Scoreboard player.scoreboard = Scoreboard() def _cleanup_old_saves(self): - """Remove old save files if we exceed max_saves""" - save_files = self.get_save_files() + """Remove old save files if we exceed maxSaves""" + saveFiles = self.get_saveFiles() - if len(save_files) > self.max_saves: + if len(saveFiles) > self.maxSaves: # Remove oldest saves - for save_file in save_files[self.max_saves:]: + for save_file in saveFiles[self.maxSaves:]: try: os.remove(save_file["filepath"]) except Exception as e: print(f"Error removing old save {save_file['filepath']}: {e}") - def _validate_save_data(self, save_data): + def _validate_saveData(self, saveData): """Validate that save data has required structure""" try: # Check for required top-level keys - required_keys = ["player_state", "game_state", "version"] - if not all(key in save_data for key in required_keys): + requiredKeys = ["playerState", "game_state", "version"] + if not all(key in saveData for key in requiredKeys): return False - # Check player_state structure - player_required = ["xPos", "yPos", "health", "maxHealth", "lives", "coins", "saveBoneDust"] - if not all(key in save_data["player_state"] for key in player_required): + # Check playerState structure + playerRequired = ["xPos", "yPos", "health", "maxHealth", "lives", "coins", "saveBoneDust"] + if not all(key in saveData["playerState"] for key in playerRequired): return False # Check game_state structure - game_required = ["currentLevel", "currentGame", "gameStartTime", "saveTime"] - if not all(key in save_data["game_state"] for key in game_required): + gameRequired = ["currentLevel", "currentGame", "gameStartTime", "saveTime"] + if not all(key in saveData["game_state"] for key in gameRequired): return False return True @@ -349,4 +349,4 @@ class SaveManager: def has_saves(self): """Check if any save files exist""" - return len(self.get_save_files()) > 0 + return len(self.get_saveFiles()) > 0 diff --git a/src/survival_generator.py b/src/survival_generator.py index 46d787b..87c4f39 100644 --- a/src/survival_generator.py +++ b/src/survival_generator.py @@ -26,10 +26,10 @@ class SurvivalGenerator: self.weaponOverrides = {} self.customWeapons = [] # Custom weapons from level pack self.availableItems = set() # Dynamically discovered items from containers - self.loadLevelData() - self.parseTemplates() + self.load_level_data() + self.parse_templates() - def loadLevelData(self): + def load_level_data(self): """Load all level JSON files from the game pack.""" levelFiles = [] packPath = os.path.join(get_levels_base_path(), "levels", self.gamePack) @@ -49,7 +49,7 @@ class SurvivalGenerator: levelNum = int(levelFile.split(".")[0]) self.levelData[levelNum] = json.load(f) - def parseTemplates(self): + def parse_templates(self): """Parse all level data to extract object templates by type.""" for levelNum, data in self.levelData.items(): # Store ambience and footstep sounds (remove duplicates) @@ -74,14 +74,14 @@ class SurvivalGenerator: # Discover items from containers (graves and coffins) if obj.get("type") in ["grave", "coffin"] and "item" in obj: - item_name = obj["item"] + itemName = obj["item"] # Check for sound override (themed equivalent) if "sound_overrides" in obj and "item" in obj["sound_overrides"]: - item_name = obj["sound_overrides"]["item"] + itemName = obj["sound_overrides"]["item"] # Exclude special items that should remain rare/exclusive - if item_name and item_name != "random" and item_name not in ["extra_life"]: - self.availableItems.add(item_name) + if itemName and itemName != "random" and itemName not in ["extra_life"]: + self.availableItems.add(itemName) # Categorize objects if "enemy_type" in obj: @@ -154,16 +154,16 @@ class SurvivalGenerator: # Guarantee at least one coffin per wave if coffins exist in templates coffinTemplates = [obj for obj in self.objectTemplates if obj.get("type") == "coffin"] - coffin_placed = False + coffinPlaced = False if coffinTemplates: # Place guaranteed coffin in the first quarter of the level - coffin_x = random.randint(startBufferZone + 10, (segmentLength - endBufferZone) // 4) - coffin_template = random.choice(coffinTemplates) - coffin_obj = copy.deepcopy(coffin_template) - coffin_obj["item"] = "random" # Override any specified item - coffin_obj["x"] = coffin_x - levelData["objects"].append(coffin_obj) - coffin_placed = True + coffinX = random.randint(startBufferZone + 10, (segmentLength - endBufferZone) // 4) + coffinTemplate = random.choice(coffinTemplates) + coffinObj = copy.deepcopy(coffinTemplate) + coffinObj["item"] = "random" # Override any specified item + coffinObj["x"] = coffinX + levelData["objects"].append(coffinObj) + coffinPlaced = True while currentX < segmentLength - endBufferZone: # Determine what to place based on probability diff --git a/wicked_quest.py b/wicked_quest.py index b7a0cfc..e56af8c 100755 --- a/wicked_quest.py +++ b/wicked_quest.py @@ -64,35 +64,35 @@ class WickedQuest: return None # Find active enemies - active_enemies = [] + activeEnemies = [] for enemy in self.currentLevel.enemies: if enemy.isActive: distance = abs(enemy.xPos - self.player.xPos) direction = "right" if enemy.xPos > self.player.xPos else "left" - active_enemies.append((enemy, distance, direction)) + activeEnemies.append((enemy, distance, direction)) - if not active_enemies: + if not activeEnemies: return None # Sort by distance and get closest - active_enemies.sort(key=lambda x: x[1]) - enemy, distance, direction = active_enemies[0] + activeEnemies.sort(key=lambda x: x[1]) + enemy, distance, direction = activeEnemies[0] # Convert distance to natural language if distance == 0: return f"{enemy.enemyType} right on top of you" elif distance <= 10: - distance_desc = "very close" + distanceDesc = "very close" elif distance <= 30: - distance_desc = "close" + distanceDesc = "close" elif distance <= 60: - distance_desc = "far" + distanceDesc = "far" elif distance <= 100: - distance_desc = "very far" + distanceDesc = "very far" else: - distance_desc = "extremely far" + distanceDesc = "extremely far" - return f"{enemy.enemyType} {distance_desc} to the {direction}" + return f"{enemy.enemyType} {distanceDesc} to the {direction}" def process_console_command(self, command): """Process console commands and execute their effects.""" @@ -132,9 +132,9 @@ class WickedQuest: enemyCount = len([enemy for enemy in self.currentLevel.enemies if enemy.isActive]) if enemyCount > 0: speak(f"{enemyCount} enemies remaining on this level") - closest_enemy_info = self.get_closest_enemy_info() - if closest_enemy_info: - speak(f"Closest enemy: {closest_enemy_info}") + closestEnemyInfo = self.get_closest_enemy_info() + if closestEnemyInfo: + speak(f"Closest enemy: {closestEnemyInfo}") else: speak("No active enemies on this level") else: @@ -190,14 +190,14 @@ class WickedQuest: elif command == "diemonsterdie": # Give 100 jack o'lanterns if self.player: - self.player._jack_o_lantern_count += 100 + self.player._jackOLanternCount += 100 speak(f"100 jack o'lanterns granted. You now have {self.player.get_jack_o_lanterns()} jack o'lanterns") if 'get_jack_o_lantern' in self.get_sounds(): play_sound(self.get_sounds()['get_jack_o_lantern']) elif command == "murderland": # Set jack o'lanterns to exactly 13 (the special number) if self.player: - self.player._jack_o_lantern_count = 13 + self.player._jackOLanternCount = 13 speak("13 jack o'lanterns, this power courses through my soul.") if 'get_jack_o_lantern' in self.get_sounds(): play_sound(self.get_sounds()['get_jack_o_lantern']) @@ -238,7 +238,7 @@ class WickedQuest: self.currentLevel.levelId, self.gameStartTime, self.currentGame, - bypass_cost=True # Skip bone dust requirement + bypassCost=True # Skip bone dust requirement ) if success: @@ -273,7 +273,7 @@ class WickedQuest: self.player._saveBoneDust = 999 # Max jack o'lanterns (set to 99 for practicality) - self.player._jack_o_lantern_count = 99 + self.player._jackOLanternCount = 99 # Grant all weapons with level pack override support weaponOverrides = getattr(self.currentLevel, 'weaponSoundOverrides', {}) if self.currentLevel else {} @@ -438,29 +438,29 @@ class WickedQuest: def load_game_menu(self): """Display load game menu with available saves using instruction_menu""" - save_files = self.saveManager.get_save_files() - - if not save_files: + saveFiles = self.saveManager.get_save_files() + + if not saveFiles: messagebox("No save files found.") return None - + # Create menu options options = [] - for save_file in save_files: - options.append(save_file['display_name']) - + for saveFile in saveFiles: + options.append(saveFile['display_name']) + options.append("Cancel") - + # Use instruction_menu for consistent behavior choice = instruction_menu(self.get_sounds(), "Select a save file to load:", *options) - + if choice == "Cancel" or choice is None: return None else: # Find the corresponding save file - for save_file in save_files: - if save_file['display_name'] == choice: - return save_file + for saveFile in saveFiles: + if saveFile['display_name'] == choice: + return saveFile return None def auto_save(self): @@ -559,56 +559,56 @@ class WickedQuest: elif keys[pygame.K_e]: # Weapon and ammo status if player.currentWeapon: - weapon_name = getattr(player.currentWeapon, 'displayName', player.currentWeapon.name.replace("_", " ")) - status_message = f"Wielding {weapon_name}" + weaponName = getattr(player.currentWeapon, 'displayName', player.currentWeapon.name.replace("_", " ")) + statusMessage = f"Wielding {weaponName}" # Check if it's a projectile weapon - always show ammo for projectile weapons - weapon_type = getattr(player.currentWeapon, 'weaponType', 'melee') - if weapon_type == "projectile": - ammo_type = getattr(player.currentWeapon, 'ammoType', None) - if ammo_type: - ammo_count = 0 - ammo_display_name = ammo_type.replace("_", " ") # Default fallback + weaponType = getattr(player.currentWeapon, 'weaponType', 'melee') + if weaponType == "projectile": + ammoType = getattr(player.currentWeapon, 'ammoType', None) + if ammoType: + ammoCount = 0 + ammoDisplayName = ammoType.replace("_", " ") # Default fallback # Get current ammo count based on ammo type - if ammo_type == "bone_dust": - ammo_count = player.get_coins() - ammo_display_name = "bone dust" - elif ammo_type == "shin_bone": - ammo_count = player.shinBoneCount - ammo_display_name = "shin bones" - elif ammo_type == "jack_o_lantern": - ammo_count = player._jack_o_lantern_count - ammo_display_name = "jack o'lanterns" - elif ammo_type == "guts": - ammo_count = player.collectedItems.count("guts") - ammo_display_name = "guts" - elif ammo_type == "hand_of_glory": - ammo_count = player.collectedItems.count("hand_of_glory") - ammo_display_name = "hands of glory" + if ammoType == "bone_dust": + ammoCount = player.get_coins() + ammoDisplayName = "bone dust" + elif ammoType == "shin_bone": + ammoCount = player.shinBoneCount + ammoDisplayName = "shin bones" + elif ammoType == "jack_o_lantern": + ammoCount = player._jackOLanternCount + ammoDisplayName = "jack o'lanterns" + elif ammoType == "guts": + ammoCount = player.collectedItems.count("guts") + ammoDisplayName = "guts" + elif ammoType == "hand_of_glory": + ammoCount = player.collectedItems.count("hand_of_glory") + ammoDisplayName = "hands of glory" else: # Check for any other item type in collectedItems - ammo_count = player.collectedItems.count(ammo_type) + ammoCount = player.collectedItems.count(ammoType) - status_message += f". {ammo_count} {ammo_display_name}" + statusMessage += f". {ammoCount} {ammoDisplayName}" - speak(status_message) + speak(statusMessage) else: speak("No weapon equipped") elif keys[pygame.K_h]: speak(f"{player.get_health()} health of {player.get_max_health()}") elif keys[pygame.K_i]: if self.currentLevel.levelId == 999: - base_info = f"Wave {self.survivalWave}. {player.get_health()} health of {player.get_max_health()}. {int(self.currentLevel.levelScore)} points on this wave so far. {player.get_lives()} lives remaining." + baseInfo = f"Wave {self.survivalWave}. {player.get_health()} health of {player.get_max_health()}. {int(self.currentLevel.levelScore)} points on this wave so far. {player.get_lives()} lives remaining." else: - base_info = f"Level {self.currentLevel.levelId}, {self.currentLevel.levelName}. {player.get_health()} health of {player.get_max_health()}. {int(self.currentLevel.levelScore)} points on this level so far. {player.get_lives()} lives remaining." + baseInfo = f"Level {self.currentLevel.levelId}, {self.currentLevel.levelName}. {player.get_health()} health of {player.get_max_health()}. {int(self.currentLevel.levelScore)} points on this level so far. {player.get_lives()} lives remaining." # Add closest enemy info - closest_enemy_info = self.get_closest_enemy_info() - if closest_enemy_info: - speak(f"{base_info} {closest_enemy_info}") + closestEnemyInfo = self.get_closest_enemy_info() + if closestEnemyInfo: + speak(f"{baseInfo} {closestEnemyInfo}") else: - speak(base_info) + speak(baseInfo) if keys[pygame.K_l]: speak(f"{player.get_lives()} lives") if keys[pygame.K_j]: # Check jack o'lanterns @@ -898,39 +898,39 @@ class WickedQuest: while True: # Add load game option if saves exist - custom_options = [] + customOptions = [] if self.saveManager.has_saves(): - custom_options.append("load_game") - - choice = game_menu(self.get_sounds(), None, *custom_options) + customOptions.append("load_game") + + choice = game_menu(self.get_sounds(), None, *customOptions) if choice == "exit": exit_game() elif choice == "load_game": - selected_save = self.load_game_menu() - if selected_save: - success, save_data = self.saveManager.load_save(selected_save['filepath']) + selectedSave = self.load_game_menu() + if selectedSave: + success, saveData = self.saveManager.load_save(selectedSave['filepath']) if success: # Load the saved game - self.currentGame = save_data['game_state']['currentGame'] - self.gameStartTime = save_data['game_state']['gameStartTime'] - current_level = save_data['game_state']['currentLevel'] + self.currentGame = saveData['game_state']['currentGame'] + self.gameStartTime = saveData['game_state']['gameStartTime'] + currentLevel = saveData['game_state']['currentLevel'] # Initialize pack-specific sound system self.initialize_pack_sounds() - + # Load the level - if self.load_level(current_level): + if self.load_level(currentLevel): # Restore player state - self.saveManager.restore_player_state(self.player, save_data) + self.saveManager.restore_player_state(self.player, saveData) # Re-apply weapon overrides after restoring player state to ensure # sound/name overrides work with restored weapon properties if hasattr(self.currentLevel, 'weaponOverrides') and self.currentLevel.weaponOverrides: self.currentLevel._apply_weapon_overrides(self.currentLevel.weaponOverrides) - self.game_loop(current_level) + self.game_loop(currentLevel) else: messagebox("Failed to load saved level.") else: - messagebox(f"Failed to load save: {save_data}") + messagebox(f"Failed to load save: {saveData}") elif choice == "play": self.currentGame = select_game(self.get_sounds()) if self.currentGame is None: @@ -947,13 +947,13 @@ class WickedQuest: continue if self.currentGame: # Ask player to choose game mode - mode_choice = game_mode_menu(self.get_sounds(), self.currentGame) - if mode_choice == "story": + modeChoice = game_mode_menu(self.get_sounds(), self.currentGame) + if modeChoice == "story": self.player = None # Reset player for new game self.gameStartTime = pygame.time.get_ticks() if self.load_level(1): self.game_loop() - elif mode_choice == "survival": + elif modeChoice == "survival": self.start_survival_mode() elif choice == "high_scores": board = Scoreboard() @@ -1116,12 +1116,12 @@ class WickedQuest: self.currentLevel = Level(levelData, self.get_sounds(), self.player, self.currentGame) -def game_mode_menu(sounds, game_dir=None): +def game_mode_menu(sounds, gameDir=None): """Display game mode selection menu using instruction_menu. Args: sounds (dict): Dictionary of loaded sound effects - game_dir (str): Current game directory to check for instructions/credits + gameDir (str): Current game directory to check for instructions/credits Returns: str: Selected game mode or None if cancelled @@ -1130,79 +1130,63 @@ def game_mode_menu(sounds, game_dir=None): import os # Build base menu options - menu_options = ["Story", "Survival Mode"] + menuOptions = ["Story", "Survival Mode"] # Check for level pack specific files if game directory is provided - if game_dir: + if gameDir: try: - game_path = get_game_dir_path(game_dir) + gamePath = get_game_dir_path(gameDir) # Check for instructions.txt - instructions_path = os.path.join(game_path, "instructions.txt") - if os.path.exists(instructions_path): - menu_options.append("Instructions") + instructionsPath = os.path.join(gamePath, "instructions.txt") + if os.path.exists(instructionsPath): + menuOptions.append("Instructions") # Check for credits.txt - credits_path = os.path.join(game_path, "credits.txt") - if os.path.exists(credits_path): - menu_options.append("Credits") + creditsPath = os.path.join(gamePath, "credits.txt") + if os.path.exists(creditsPath): + menuOptions.append("Credits") except Exception: # If there's any error checking files, just continue with basic menu pass while True: - choice = instruction_menu(sounds, "Select game mode:", *menu_options) + choice = instruction_menu(sounds, "Select game mode:", *menuOptions) if choice == "Story": return "story" elif choice == "Survival Mode": return "survival" - elif choice == "Instructions" and game_dir: + elif choice == "Instructions" and gameDir: # Display instructions file try: - game_path = get_game_dir_path(game_dir) - instructions_path = os.path.join(game_path, "instructions.txt") - print(f"DEBUG: Looking for instructions at: {instructions_path}") - if os.path.exists(instructions_path): - print("DEBUG: Instructions file found, loading content...") - with open(instructions_path, 'r', encoding='utf-8') as f: - instructions_content = f.read() - print(f"DEBUG: Content length: {len(instructions_content)} characters") - print("DEBUG: Calling display_text...") + gamePath = get_game_dir_path(gameDir) + instructionsPath = os.path.join(gamePath, "instructions.txt") + if os.path.exists(instructionsPath): + with open(instructionsPath, 'r', encoding='utf-8') as f: + instructionsContent = f.read() # Convert string to list of lines for display_text - content_lines = instructions_content.split('\n') - print(f"DEBUG: Split into {len(content_lines)} lines") - display_text(content_lines) - print("DEBUG: display_text returned") + contentLines = instructionsContent.split('\n') + display_text(contentLines) else: - print("DEBUG: Instructions file not found at expected path") speak("Instructions file not found") except Exception as e: - print(f"DEBUG: Error loading instructions: {str(e)}") speak(f"Error loading instructions: {str(e)}") - elif choice == "Credits" and game_dir: + elif choice == "Credits" and gameDir: # Display credits file try: - game_path = get_game_dir_path(game_dir) - credits_path = os.path.join(game_path, "credits.txt") - print(f"DEBUG: Looking for credits at: {credits_path}") - if os.path.exists(credits_path): - print("DEBUG: Credits file found, loading content...") - with open(credits_path, 'r', encoding='utf-8') as f: - credits_content = f.read() - print(f"DEBUG: Content length: {len(credits_content)} characters") - print("DEBUG: Calling display_text...") + gamePath = get_game_dir_path(gameDir) + creditsPath = os.path.join(gamePath, "credits.txt") + if os.path.exists(creditsPath): + with open(creditsPath, 'r', encoding='utf-8') as f: + creditsContent = f.read() # Convert string to list of lines for display_text - content_lines = credits_content.split('\n') - print(f"DEBUG: Split into {len(content_lines)} lines") - display_text(content_lines) - print("DEBUG: display_text returned") + contentLines = creditsContent.split('\n') + display_text(contentLines) else: - print("DEBUG: Credits file not found at expected path") speak("Credits file not found") except Exception as e: - print(f"DEBUG: Error loading credits: {str(e)}") speak(f"Error loading credits: {str(e)}") else: return None