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