Updates to libstormgames submodule. Updated the game to use the menu improvements. Started work on survival mode.
This commit is contained in:
		 Submodule libstormgames updated: ca2d0d34bd...dcd204e476
									
								
							| @@ -1,10 +1,11 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| import os | import os | ||||||
|  | import sys | ||||||
| import time | import time | ||||||
| import pygame | import pygame | ||||||
| from os.path import isdir, join | from os.path import isdir, join | ||||||
| from libstormgames import speak | from libstormgames import speak, instruction_menu | ||||||
|  |  | ||||||
| def get_available_games(): | def get_available_games(): | ||||||
|     """Get list of available game directories in levels folder. |     """Get list of available game directories in levels folder. | ||||||
| @@ -13,12 +14,21 @@ def get_available_games(): | |||||||
|         list: List of game directory names |         list: List of game directory names | ||||||
|     """ |     """ | ||||||
|     try: |     try: | ||||||
|         return [d for d in os.listdir("levels") if isdir(join("levels", d))] |         # Handle PyInstaller path issues | ||||||
|  |         if hasattr(sys, '_MEIPASS'): | ||||||
|  |             # Running as PyInstaller executable | ||||||
|  |             base_path = sys._MEIPASS | ||||||
|  |         else: | ||||||
|  |             # Running as script | ||||||
|  |             base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||||
|  |          | ||||||
|  |         levels_path = os.path.join(base_path, "levels") | ||||||
|  |         return [d for d in os.listdir(levels_path) if isdir(join(levels_path, d))] | ||||||
|     except FileNotFoundError: |     except FileNotFoundError: | ||||||
|         return [] |         return [] | ||||||
|  |  | ||||||
| def selection_menu(sounds, *options): | def selection_menu(sounds, *options): | ||||||
|     """Display level selection menu. |     """Display level selection menu using instruction_menu. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         sounds (dict): Dictionary of loaded sound effects |         sounds (dict): Dictionary of loaded sound effects | ||||||
| @@ -27,70 +37,7 @@ def selection_menu(sounds, *options): | |||||||
|     Returns: |     Returns: | ||||||
|         str: Selected option or None if cancelled |         str: Selected option or None if cancelled | ||||||
|     """ |     """ | ||||||
|     loop = True |     return instruction_menu(sounds, "Select an adventure", *options) | ||||||
|     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): | def select_game(sounds): | ||||||
|     """Display game selection menu and return chosen game. |     """Display game selection menu and return chosen game. | ||||||
| @@ -134,4 +81,14 @@ def get_level_path(gameDir, levelNum): | |||||||
|     """ |     """ | ||||||
|     if gameDir is None: |     if gameDir is None: | ||||||
|         raise ValueError("gameDir cannot be None") |         raise ValueError("gameDir cannot be None") | ||||||
|     return os.path.join("levels", gameDir, f"{levelNum}.json") |      | ||||||
|  |     # Handle PyInstaller path issues | ||||||
|  |     if hasattr(sys, '_MEIPASS'): | ||||||
|  |         # Running as PyInstaller executable | ||||||
|  |         base_path = sys._MEIPASS | ||||||
|  |     else: | ||||||
|  |         # Running as script | ||||||
|  |         base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||||
|  |      | ||||||
|  |     level_path = os.path.join(base_path, "levels", gameDir, f"{levelNum}.json") | ||||||
|  |     return level_path | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/level.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/level.py
									
									
									
									
									
								
							| @@ -40,12 +40,13 @@ class Level: | |||||||
|         # Pass footstep sound to player |         # Pass footstep sound to player | ||||||
|         self.player.set_footstep_sound(self.footstepSound) |         self.player.set_footstep_sound(self.footstepSound) | ||||||
|  |  | ||||||
|         # Level intro message |         # Level intro message (skip for survival mode) | ||||||
|         levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. " |         if levelData['level_id'] != 999:  # 999 is survival mode | ||||||
|         if self.isLocked: |             levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. " | ||||||
|             levelIntro += "This is a boss level. You must defeat all enemies before you can advance. " |             if self.isLocked: | ||||||
|         levelIntro += levelData['description'] |                 levelIntro += "This is a boss level. You must defeat all enemies before you can advance. " | ||||||
|         messagebox(levelIntro) |             levelIntro += levelData['description'] | ||||||
|  |             messagebox(levelIntro) | ||||||
|  |  | ||||||
|         # Handle level music |         # Handle level music | ||||||
|         try: |         try: | ||||||
|   | |||||||
							
								
								
									
										224
									
								
								src/survival_generator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								src/survival_generator.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | import random | ||||||
|  | import copy | ||||||
|  | from src.game_selection import get_level_path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SurvivalGenerator: | ||||||
|  |     def __init__(self, gamePack): | ||||||
|  |         """Initialize the survival generator for a specific game pack. | ||||||
|  |          | ||||||
|  |         Args: | ||||||
|  |             gamePack (str): Name of the game pack directory | ||||||
|  |         """ | ||||||
|  |         self.gamePack = gamePack | ||||||
|  |         self.levelData = {} | ||||||
|  |         self.objectTemplates = [] | ||||||
|  |         self.enemyTemplates = [] | ||||||
|  |         self.collectibleTemplates = [] | ||||||
|  |         self.hazardTemplates = [] | ||||||
|  |         self.ambientSounds = [] | ||||||
|  |         self.footstepSounds = [] | ||||||
|  |         self.loadLevelData() | ||||||
|  |         self.parseTemplates() | ||||||
|  |      | ||||||
|  |     def loadLevelData(self): | ||||||
|  |         """Load all level JSON files from the game pack.""" | ||||||
|  |         levelFiles = [] | ||||||
|  |         packPath = os.path.join("levels", self.gamePack) | ||||||
|  |          | ||||||
|  |         if not os.path.exists(packPath): | ||||||
|  |             raise FileNotFoundError(f"Game pack '{self.gamePack}' not found") | ||||||
|  |          | ||||||
|  |         # Get all JSON files in the pack directory | ||||||
|  |         for file in os.listdir(packPath): | ||||||
|  |             if file.endswith('.json') and file[0].isdigit(): | ||||||
|  |                 levelFiles.append(file) | ||||||
|  |          | ||||||
|  |         # Load each level file | ||||||
|  |         for levelFile in levelFiles: | ||||||
|  |             levelPath = os.path.join(packPath, levelFile) | ||||||
|  |             with open(levelPath, 'r') as f: | ||||||
|  |                 levelNum = int(levelFile.split('.')[0]) | ||||||
|  |                 self.levelData[levelNum] = json.load(f) | ||||||
|  |      | ||||||
|  |     def parseTemplates(self): | ||||||
|  |         """Parse all level data to extract object templates by type.""" | ||||||
|  |         for levelNum, data in self.levelData.items(): | ||||||
|  |             # Store ambience and footstep sounds | ||||||
|  |             if 'ambience' in data: | ||||||
|  |                 self.ambientSounds.append(data['ambience']) | ||||||
|  |             if 'footstep_sound' in data: | ||||||
|  |                 self.footstepSounds.append(data['footstep_sound']) | ||||||
|  |              | ||||||
|  |             # Parse objects | ||||||
|  |             for obj in data.get('objects', []): | ||||||
|  |                 objCopy = copy.deepcopy(obj) | ||||||
|  |                  | ||||||
|  |                 # Categorize objects | ||||||
|  |                 if 'enemy_type' in obj: | ||||||
|  |                     self.enemyTemplates.append(objCopy) | ||||||
|  |                 elif obj.get('collectible', False) or obj.get('sound') == 'bone_dust': | ||||||
|  |                     self.collectibleTemplates.append(objCopy) | ||||||
|  |                 elif obj.get('type') in ['skull_storm', 'catapult', 'grasping_hands']: | ||||||
|  |                     self.hazardTemplates.append(objCopy) | ||||||
|  |                 else: | ||||||
|  |                     self.objectTemplates.append(objCopy) | ||||||
|  |      | ||||||
|  |     def generate_survival_level(self, difficultyLevel=1, segmentLength=100): | ||||||
|  |         """Generate an endless survival level segment. | ||||||
|  |          | ||||||
|  |         Args: | ||||||
|  |             difficultyLevel (int): Current difficulty level (increases over time) | ||||||
|  |             segmentLength (int): Length of this level segment | ||||||
|  |              | ||||||
|  |         Returns: | ||||||
|  |             dict: Generated level data | ||||||
|  |         """ | ||||||
|  |         # Base level structure | ||||||
|  |         levelData = { | ||||||
|  |             "level_id": 999,  # Special ID for survival mode | ||||||
|  |             "name": f"Wave {difficultyLevel}", | ||||||
|  |             "description": "",  # Empty description to avoid automatic messagebox | ||||||
|  |             "player_start": {"x": 0, "y": 0}, | ||||||
|  |             "objects": [], | ||||||
|  |             "boundaries": {"left": 0, "right": segmentLength}, | ||||||
|  |             "ambience": "Escaping the Grave.ogg",  # Will be overridden below | ||||||
|  |             "footstep_sound": "footstep_stone"  # Will be overridden below | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         # Choose random music and footstep from any level | ||||||
|  |         randomLevel = random.choice(list(self.levelData.values())) | ||||||
|  |         if 'ambience' in randomLevel and randomLevel['ambience']: | ||||||
|  |             levelData["ambience"] = randomLevel['ambience'] | ||||||
|  |         if 'footstep_sound' in randomLevel and randomLevel['footstep_sound']: | ||||||
|  |             levelData["footstep_sound"] = randomLevel['footstep_sound'] | ||||||
|  |          | ||||||
|  |         # Calculate spawn rates based on difficulty | ||||||
|  |         collectibleDensity = max(0.1, 0.3 - (difficultyLevel * 0.02))  # Fewer collectibles over time | ||||||
|  |         enemyDensity = min(0.8, 0.2 + (difficultyLevel * 0.05))  # More enemies over time   | ||||||
|  |         hazardDensity = min(0.4, 0.1 + (difficultyLevel * 0.03))  # More hazards over time | ||||||
|  |         objectDensity = max(0.1, 0.2 - (difficultyLevel * 0.01))  # Fewer misc objects over time | ||||||
|  |          | ||||||
|  |         # Generate objects across the segment | ||||||
|  |         currentX = 10  # Start placing objects at x=10 | ||||||
|  |          | ||||||
|  |         while currentX < segmentLength - 10: | ||||||
|  |             # Determine what to place based on probability | ||||||
|  |             rand = random.random() | ||||||
|  |              | ||||||
|  |             if rand < collectibleDensity and self.collectibleTemplates: | ||||||
|  |                 obj = self.place_collectible(currentX, difficultyLevel) | ||||||
|  |                 currentX += random.randint(8, 15) | ||||||
|  |             elif rand < collectibleDensity + enemyDensity and self.enemyTemplates: | ||||||
|  |                 obj = self.place_enemy(currentX, difficultyLevel) | ||||||
|  |                 currentX += random.randint(15, 25) | ||||||
|  |             elif rand < collectibleDensity + enemyDensity + hazardDensity and self.hazardTemplates: | ||||||
|  |                 obj = self.place_hazard(currentX, difficultyLevel) | ||||||
|  |                 currentX += random.randint(20, 35) | ||||||
|  |             elif rand < collectibleDensity + enemyDensity + hazardDensity + objectDensity and self.objectTemplates: | ||||||
|  |                 obj = self.place_object(currentX, difficultyLevel) | ||||||
|  |                 currentX += random.randint(12, 20) | ||||||
|  |             else: | ||||||
|  |                 currentX += random.randint(5, 15) | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             if obj: | ||||||
|  |                 levelData["objects"].append(obj) | ||||||
|  |          | ||||||
|  |         return levelData | ||||||
|  |      | ||||||
|  |     def place_collectible(self, xPos, difficultyLevel): | ||||||
|  |         """Place a collectible at the given position.""" | ||||||
|  |         template = random.choice(self.collectibleTemplates) | ||||||
|  |         obj = copy.deepcopy(template) | ||||||
|  |          | ||||||
|  |         # Handle x_range vs single x | ||||||
|  |         if 'x_range' in obj: | ||||||
|  |             rangeSize = obj['x_range'][1] - obj['x_range'][0] | ||||||
|  |             obj['x_range'] = [xPos, xPos + rangeSize] | ||||||
|  |         else: | ||||||
|  |             obj['x'] = xPos | ||||||
|  |          | ||||||
|  |         return obj | ||||||
|  |      | ||||||
|  |     def place_enemy(self, xPos, difficultyLevel): | ||||||
|  |         """Place an enemy at the given position with scaled difficulty.""" | ||||||
|  |         # Filter out boss enemies for early waves | ||||||
|  |         bossEnemies = ['witch', 'boogie_man', 'revenant', 'ghost', 'headless_horseman'] | ||||||
|  |          | ||||||
|  |         if difficultyLevel < 3:  # Waves 1-2: no bosses | ||||||
|  |             availableEnemies = [e for e in self.enemyTemplates  | ||||||
|  |                               if e.get('enemy_type') not in bossEnemies] | ||||||
|  |         elif difficultyLevel < 5:  # Waves 3-4: exclude the hardest bosses | ||||||
|  |             hardestBosses = ['revenant', 'ghost', 'headless_horseman'] | ||||||
|  |             availableEnemies = [e for e in self.enemyTemplates  | ||||||
|  |                               if e.get('enemy_type') not in hardestBosses] | ||||||
|  |         else:  # Wave 5+: all enemies allowed | ||||||
|  |             availableEnemies = self.enemyTemplates | ||||||
|  |          | ||||||
|  |         # Fallback to all enemies if filtering removed everything | ||||||
|  |         if not availableEnemies: | ||||||
|  |             availableEnemies = self.enemyTemplates | ||||||
|  |          | ||||||
|  |         template = random.choice(availableEnemies) | ||||||
|  |         obj = copy.deepcopy(template) | ||||||
|  |          | ||||||
|  |         # Scale enemy stats based on difficulty | ||||||
|  |         healthMultiplier = 1 + (difficultyLevel * 0.15) | ||||||
|  |         damageMultiplier = 1 + (difficultyLevel * 0.1) | ||||||
|  |          | ||||||
|  |         obj['health'] = int(obj.get('health', 1) * healthMultiplier) | ||||||
|  |         obj['damage'] = max(1, int(obj.get('damage', 1) * damageMultiplier)) | ||||||
|  |          | ||||||
|  |         # Handle x_range vs single x | ||||||
|  |         if 'x_range' in obj: | ||||||
|  |             rangeSize = obj['x_range'][1] - obj['x_range'][0] | ||||||
|  |             obj['x_range'] = [xPos, xPos + rangeSize] | ||||||
|  |         else: | ||||||
|  |             obj['x'] = xPos | ||||||
|  |          | ||||||
|  |         return obj | ||||||
|  |      | ||||||
|  |     def place_hazard(self, xPos, difficultyLevel): | ||||||
|  |         """Place a hazard at the given position with scaled difficulty.""" | ||||||
|  |         template = random.choice(self.hazardTemplates) | ||||||
|  |         obj = copy.deepcopy(template) | ||||||
|  |          | ||||||
|  |         # Scale hazard difficulty | ||||||
|  |         if obj.get('type') == 'skull_storm': | ||||||
|  |             obj['damage'] = max(1, int(obj.get('damage', 1) * (1 + difficultyLevel * 0.1))) | ||||||
|  |             obj['maximum_skulls'] = min(6, obj.get('maximum_skulls', 2) + (difficultyLevel // 3)) | ||||||
|  |         elif obj.get('type') == 'catapult': | ||||||
|  |             obj['fire_interval'] = max(1000, obj.get('fire_interval', 4000) - (difficultyLevel * 100)) | ||||||
|  |          | ||||||
|  |         # Handle x_range vs single x   | ||||||
|  |         if 'x_range' in obj: | ||||||
|  |             rangeSize = obj['x_range'][1] - obj['x_range'][0] | ||||||
|  |             obj['x_range'] = [xPos, xPos + rangeSize] | ||||||
|  |         else: | ||||||
|  |             obj['x'] = xPos | ||||||
|  |          | ||||||
|  |         return obj | ||||||
|  |      | ||||||
|  |     def place_object(self, xPos, difficultyLevel): | ||||||
|  |         """Place a misc object at the given position.""" | ||||||
|  |         template = random.choice(self.objectTemplates) | ||||||
|  |         obj = copy.deepcopy(template) | ||||||
|  |          | ||||||
|  |         # Handle graves - increase zombie spawn chance with difficulty | ||||||
|  |         if obj.get('type') == 'grave': | ||||||
|  |             baseChance = obj.get('zombie_spawn_chance', 0) | ||||||
|  |             obj['zombie_spawn_chance'] = min(50, baseChance + (difficultyLevel * 2)) | ||||||
|  |          | ||||||
|  |         # Handle x_range vs single x | ||||||
|  |         if 'x_range' in obj: | ||||||
|  |             rangeSize = obj['x_range'][1] - obj['x_range'][0] | ||||||
|  |             obj['x_range'] = [xPos, xPos + rangeSize] | ||||||
|  |         else: | ||||||
|  |             obj['x'] = xPos | ||||||
|  |          | ||||||
|  |         return obj | ||||||
							
								
								
									
										180
									
								
								wicked_quest.py
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								wicked_quest.py
									
									
									
									
									
								
							| @@ -10,6 +10,7 @@ from src.object import Object | |||||||
| from src.player import Player | from src.player import Player | ||||||
| from src.game_selection import select_game, get_level_path | from src.game_selection import select_game, get_level_path | ||||||
| from src.save_manager import SaveManager | from src.save_manager import SaveManager | ||||||
|  | from src.survival_generator import SurvivalGenerator | ||||||
|  |  | ||||||
|  |  | ||||||
| class WickedQuest: | class WickedQuest: | ||||||
| @@ -24,6 +25,9 @@ class WickedQuest: | |||||||
|         self.currentGame = None |         self.currentGame = None | ||||||
|         self.runLock = False  # Toggle behavior of the run keys |         self.runLock = False  # Toggle behavior of the run keys | ||||||
|         self.saveManager = SaveManager() |         self.saveManager = SaveManager() | ||||||
|  |         self.survivalGenerator = None | ||||||
|  |         self.survivalWave = 1 | ||||||
|  |         self.survivalScore = 0 | ||||||
|  |  | ||||||
|     def load_level(self, levelNumber): |     def load_level(self, levelNumber): | ||||||
|         """Load a level from its JSON file.""" |         """Load a level from its JSON file.""" | ||||||
| @@ -87,7 +91,7 @@ class WickedQuest: | |||||||
|         return errors |         return errors | ||||||
|  |  | ||||||
|     def load_game_menu(self): |     def load_game_menu(self): | ||||||
|         """Display load game menu with available saves""" |         """Display load game menu with available saves using instruction_menu""" | ||||||
|         save_files = self.saveManager.get_save_files() |         save_files = self.saveManager.get_save_files() | ||||||
|          |          | ||||||
|         if not save_files: |         if not save_files: | ||||||
| @@ -101,45 +105,17 @@ class WickedQuest: | |||||||
|          |          | ||||||
|         options.append("Cancel") |         options.append("Cancel") | ||||||
|          |          | ||||||
|         # Show menu |         # Use instruction_menu for consistent behavior | ||||||
|         currentIndex = 0 |         choice = instruction_menu(self.sounds, "Select a save file to load:", *options) | ||||||
|         lastSpoken = -1 |  | ||||||
|          |          | ||||||
|         messagebox("Select a save file to load:") |         if choice == "Cancel" or choice is None: | ||||||
|          |             return None | ||||||
|         while True: |         else: | ||||||
|             if currentIndex != lastSpoken: |             # Find the corresponding save file | ||||||
|                 speak(options[currentIndex]) |             for save_file in save_files: | ||||||
|                 lastSpoken = currentIndex |                 if save_file['display_name'] == choice: | ||||||
|              |                     return save_file | ||||||
|             event = pygame.event.wait() |             return None | ||||||
|             if event.type == pygame.KEYDOWN: |  | ||||||
|                 if event.key == pygame.K_ESCAPE: |  | ||||||
|                     return None |  | ||||||
|                 elif event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(options) - 1: |  | ||||||
|                     currentIndex += 1 |  | ||||||
|                     try: |  | ||||||
|                         self.sounds['menu-move'].play() |  | ||||||
|                     except: |  | ||||||
|                         pass |  | ||||||
|                 elif event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0: |  | ||||||
|                     currentIndex -= 1 |  | ||||||
|                     try: |  | ||||||
|                         self.sounds['menu-move'].play() |  | ||||||
|                     except: |  | ||||||
|                         pass |  | ||||||
|                 elif event.key == pygame.K_RETURN: |  | ||||||
|                     try: |  | ||||||
|                         self.sounds['menu-select'].play() |  | ||||||
|                     except: |  | ||||||
|                         pass |  | ||||||
|                      |  | ||||||
|                     if currentIndex == len(options) - 1:  # Cancel |  | ||||||
|                         return None |  | ||||||
|                     else: |  | ||||||
|                         return save_files[currentIndex] |  | ||||||
|              |  | ||||||
|             pygame.event.clear() |  | ||||||
|  |  | ||||||
|     def auto_save(self): |     def auto_save(self): | ||||||
|         """Automatically save the game if player has enough bone dust""" |         """Automatically save the game if player has enough bone dust""" | ||||||
| @@ -448,10 +424,15 @@ class WickedQuest: | |||||||
|                     display_text(errorLines) |                     display_text(errorLines) | ||||||
|                     continue |                     continue | ||||||
|                 if self.currentGame: |                 if self.currentGame: | ||||||
|                     self.player = None  # Reset player for new game |                     # Ask player to choose game mode | ||||||
|                     self.gameStartTime = pygame.time.get_ticks() |                     mode_choice = game_mode_menu(self.sounds) | ||||||
|                     if self.load_level(1): |                     if mode_choice == "campaign": | ||||||
|                         self.game_loop() |                         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": | ||||||
|  |                         self.start_survival_mode() | ||||||
|             elif choice == "high_scores": |             elif choice == "high_scores": | ||||||
|                 board = Scoreboard() |                 board = Scoreboard() | ||||||
|                 scores = board.get_high_scores() |                 scores = board.get_high_scores() | ||||||
| @@ -465,6 +446,119 @@ class WickedQuest: | |||||||
|             elif choice == "learn_sounds": |             elif choice == "learn_sounds": | ||||||
|                 choice = learn_sounds(self.sounds) |                 choice = learn_sounds(self.sounds) | ||||||
|  |  | ||||||
|  |     def start_survival_mode(self): | ||||||
|  |         """Initialize and start survival mode.""" | ||||||
|  |         self.survivalGenerator = SurvivalGenerator(self.currentGame) | ||||||
|  |         self.survivalWave = 1 | ||||||
|  |         self.survivalScore = 0 | ||||||
|  |         self.player = Player(0, 0, self.sounds) | ||||||
|  |         self.gameStartTime = pygame.time.get_ticks() | ||||||
|  |          | ||||||
|  |         # Generate first survival segment | ||||||
|  |         levelData = self.survivalGenerator.generate_survival_level(self.survivalWave, 300) | ||||||
|  |         self.currentLevel = Level(levelData, self.sounds, self.player) | ||||||
|  |          | ||||||
|  |         messagebox(f"Survival Mode - Wave {self.survivalWave}! Survive as long as you can!") | ||||||
|  |         self.survival_loop() | ||||||
|  |      | ||||||
|  |     def survival_loop(self): | ||||||
|  |         """Main survival mode game loop with endless level generation.""" | ||||||
|  |         clock = pygame.time.Clock() | ||||||
|  |          | ||||||
|  |         while True: | ||||||
|  |             currentTime = pygame.time.get_ticks() | ||||||
|  |             pygame.event.pump() | ||||||
|  |              | ||||||
|  |             # Handle events | ||||||
|  |             for event in pygame.event.get(): | ||||||
|  |                 if event.type == pygame.KEYDOWN: | ||||||
|  |                     if event.key == pygame.K_ESCAPE: | ||||||
|  |                         messagebox(f"Survival ended! Final score: {self.survivalScore}") | ||||||
|  |                         return | ||||||
|  |                     elif event.key in [pygame.K_CAPSLOCK, pygame.K_TAB]: | ||||||
|  |                         self.runLock = not self.runLock | ||||||
|  |                         speak("Run lock " + ("enabled." if self.runLock else "disabled.")) | ||||||
|  |                     elif event.key == pygame.K_BACKSPACE: | ||||||
|  |                         pause_game() | ||||||
|  |                 elif event.type == pygame.QUIT: | ||||||
|  |                     exit_game() | ||||||
|  |              | ||||||
|  |             # Update game state (following main game_loop pattern) | ||||||
|  |             self.currentLevel.player.update(currentTime) | ||||||
|  |             self.handle_input() | ||||||
|  |             self.currentLevel.update_audio() | ||||||
|  |              | ||||||
|  |             # Handle combat and projectiles | ||||||
|  |             self.currentLevel.handle_combat(currentTime) | ||||||
|  |             self.currentLevel.handle_projectiles(currentTime) | ||||||
|  |              | ||||||
|  |             # Handle collisions (including collecting items) | ||||||
|  |             self.currentLevel.handle_collisions() | ||||||
|  |              | ||||||
|  |             # Check if player reached end of segment - generate new one | ||||||
|  |             if self.player.xPos >= self.currentLevel.rightBoundary - 20: | ||||||
|  |                 self.advance_survival_wave() | ||||||
|  |              | ||||||
|  |             # Check for death first (following main game loop pattern) | ||||||
|  |             if self.currentLevel.player.get_health() <= 0: | ||||||
|  |                 if self.currentLevel.player.get_lives() <= 0: | ||||||
|  |                     # Game over - stop all sounds | ||||||
|  |                     pygame.mixer.stop() | ||||||
|  |                     messagebox(f"Game Over! Final wave: {self.survivalWave}, Final score: {self.survivalScore}") | ||||||
|  |                     return | ||||||
|  |                 else: | ||||||
|  |                     # Player died but has lives left - respawn | ||||||
|  |                     pygame.mixer.stop() | ||||||
|  |                     self.currentLevel.player._health = self.currentLevel.player._maxHealth | ||||||
|  |                     # Reset player position to beginning of current segment | ||||||
|  |                     self.player.xPos = 10 | ||||||
|  |              | ||||||
|  |             # Update score based on survival time | ||||||
|  |             self.survivalScore += 1 | ||||||
|  |              | ||||||
|  |             clock.tick(60)  # 60 FPS | ||||||
|  |      | ||||||
|  |     def advance_survival_wave(self): | ||||||
|  |         """Generate next wave/segment for survival mode.""" | ||||||
|  |         self.survivalWave += 1 | ||||||
|  |          | ||||||
|  |         # Clear any lingering projectiles/sounds from previous wave | ||||||
|  |         if hasattr(self, 'currentLevel') and self.currentLevel: | ||||||
|  |             self.currentLevel.projectiles.clear() | ||||||
|  |         pygame.mixer.stop()  # Stop any ongoing catapult/enemy sounds | ||||||
|  |          | ||||||
|  |         # Generate new segment | ||||||
|  |         segmentLength = min(500, 300 + (self.survivalWave * 20))  # Longer segments over time | ||||||
|  |         levelData = self.survivalGenerator.generate_survival_level(self.survivalWave, segmentLength) | ||||||
|  |          | ||||||
|  |         # Preserve player position but shift to start of new segment | ||||||
|  |         playerX = 10 | ||||||
|  |         self.player.xPos = playerX | ||||||
|  |          | ||||||
|  |         # Create new level | ||||||
|  |         self.currentLevel = Level(levelData, self.sounds, self.player) | ||||||
|  |          | ||||||
|  |         speak(f"Wave {self.survivalWave}! Difficulty increased!") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def game_mode_menu(sounds): | ||||||
|  |     """Display game mode selection menu using instruction_menu. | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         sounds (dict): Dictionary of loaded sound effects | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         str: Selected game mode or None if cancelled | ||||||
|  |     """ | ||||||
|  |     choice = instruction_menu(sounds, "Select game mode:", "Campaign", "Survival Mode") | ||||||
|  |      | ||||||
|  |     if choice == "Campaign": | ||||||
|  |         return "campaign" | ||||||
|  |     elif choice == "Survival Mode": | ||||||
|  |         return "survival" | ||||||
|  |     else: | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     game = WickedQuest() |     game = WickedQuest() | ||||||
|   | |||||||
| @@ -5,7 +5,11 @@ a = Analysis( | |||||||
|     ['wicked_quest.py'], |     ['wicked_quest.py'], | ||||||
|     pathex=[], |     pathex=[], | ||||||
|     binaries=[], |     binaries=[], | ||||||
|     datas=[], |     datas=[ | ||||||
|  |         ('levels', 'levels'), | ||||||
|  |         ('sounds', 'sounds'), | ||||||
|  |         ('libstormgames', 'libstormgames'), | ||||||
|  |     ], | ||||||
|     hiddenimports=[], |     hiddenimports=[], | ||||||
|     hookspath=[], |     hookspath=[], | ||||||
|     hooksconfig={}, |     hooksconfig={}, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user