Added override capabilities for lots of items, weapons, and hazards.

This commit is contained in:
Storm Dragon
2025-09-20 04:10:32 -04:00
parent 91eecae786
commit 5dd78a1687
12 changed files with 392 additions and 43 deletions
+177 -3
View File
@@ -81,7 +81,7 @@ Instead of using a simple `"description"` field, levels can now include interact
- `narrative`: Set to `true` for descriptive text entries (no speaker)
- `sound`: Optional sound file to play with this dialogue entry. If no sound is specified, the system will automatically play `sounds/dialogue.ogg` if it exists.
**Note:** Levels can use either `"description"` (traditional format) or `"dialog"` (new interactive format), but not both. The dialogue system takes precedence if both are present.
**Note:** Levels can include both `"description"` and `"dialog"`. When both are present, the dialogue plays first, followed by the standard level description message format ("Level X, Name. Description.").
## Adding Objects
@@ -385,13 +385,187 @@ sounds/Samhain Showdown/ambience
├── howling_winds.ogg
```
### Sound Override System
### Advanced Sound Override System
Beyond simple file replacement, Wicked Quest now supports granular sound overrides directly in your level JSON files. This allows thematic consistency where a catapult becomes a "snowball launcher" or grasping hands become an "avalanche" - same mechanics, different sounds and feel.
#### Weapon Sound Overrides
Override weapon sounds and names globally for an entire level:
```json
{
"level_id": 1,
"name": "Winter Wonderland",
"weapon_sound_overrides": {
"rusty_shovel": {
"name": "rusty snow shovel",
"attack_sound": "player_snow_shovel_attack",
"hit_sound": "player_snow_shovel_hit"
},
"nunchucks": {
"name": "ice sickles",
"attack_sound": "player_ice_sickles_attack",
"hit_sound": "player_ice_sickles_hit"
},
"witch_broom": {
"name": "snow broom",
"attack_sound": "player_snow_broom_attack",
"hit_sound": "player_snow_broom_hit"
}
}
}
```
**Weapon Override Properties:**
- `name`: Display name for the weapon (e.g., "ice sickles" instead of "nunchucks")
- `attack_sound`: Sound played when attacking
- `hit_sound`: Sound played when hitting an enemy
#### Object Sound Overrides
Override sounds for individual objects in your level:
```json
{
"x": 25,
"y": 0,
"type": "catapult",
"fire_interval": 3000,
"range": 30,
"sound_overrides": {
"base": "snowball_launcher",
"launch": "snowball_launcher_launch"
}
}
```
```json
{
"x_range": [40, 60],
"y": 0,
"type": "grasping_hands",
"delay": 1000,
"sound_overrides": {
"base": "avalanche"
}
}
```
```json
{
"x": 35,
"y": 0,
"type": "grave",
"item": "shin_bone",
"sound_overrides": {
"base": "snow_mound",
"item": "candy_cane"
}
}
```
**Object Sound Override Properties:**
- `base`: Override the main ambient sound (e.g., "catapult" → "snowball_launcher")
- `launch`: Override launch sound for catapults (e.g., "catapult_launch" → "snowball_launcher_launch")
- `item`: Override pickup sound for grave items (e.g., "get_shin_bone.ogg" → "get_candy_cane.ogg")
- `warning_message`: Override warning message for grasping hands (e.g., "The ground crumbles as snow begins to avalanche!")
- `death_message`: Override death message for grasping hands (e.g., "You vanish under tons of snow!")
#### Themed Item Equivalents
The sound override system includes intelligent item mapping for crafting consistency. Certain themed items automatically behave like their original counterparts:
**Christmas Theme:**
- `"candy_cane"` → Functions as `"shin_bone"` (increments shin bone count)
- `"reindeer_guts"` → Functions as `"guts"` (enables nunchucks crafting)
**Result:** Collecting 2 candy canes + reindeer guts = nunchucks (can be renamed to "ice sickles")
This system allows complete thematic consistency where players collect "2 Candy Canes + Reindeer Guts = Ice Sickles" while preserving all original game mechanics. The mapping works automatically across any level pack - simply use themed item names and they'll function correctly.
**Adding New Themed Equivalents:**
To add your own themed items, modify the `themed_mappings` in `src/powerup.py`:
```python
themed_mappings = {
"your_bone_item": "shin_bone",
"your_guts_item": "guts",
}
```
#### Complete Thematic Example
Here's how to transform a Halloween level into a Christmas level using sound overrides:
```json
{
"level_id": 1,
"name": "Winter Siege",
"description": "Santa's workshop is under attack by snow witches!",
"weapon_sound_overrides": {
"rusty_shovel": {
"name": "snow shovel",
"attack_sound": "player_snow_shovel_attack",
"hit_sound": "player_snow_shovel_hit"
}
},
"objects": [
{
"x": 25,
"y": 0,
"type": "catapult",
"sound_overrides": {
"base": "snowball_launcher",
"launch": "snowball_launcher_launch"
}
},
{
"x_range": [40, 60],
"y": 12,
"type": "skull_storm",
"sound_overrides": {
"base": "snowball_storm"
}
},
{
"x": 75,
"y": 0,
"type": "grave",
"item": "shin_bone",
"sound_overrides": {
"base": "snow_pile",
"item": "candy_cane"
}
},
{
"x_range": [90, 110],
"y": 0,
"type": "grasping_hands",
"sound_overrides": {
"base": "avalanche",
"warning_message": "The ground crumbles as snow begins to avalanche!",
"death_message": "You vanish under tons of snow!"
}
}
]
}
```
**Result:**
- Weapons sound winter-themed when attacking
- "Catapult" becomes "Snowball Launcher" with appropriate launch sounds
- "Skull Storm" becomes "Snowball Storm"
- "Graves" become "Snow Piles" containing "Candy Canes" instead of "Shin Bones"
- "Grasping Hands" becomes "Avalanche" with snow-themed death messages
- All mechanics remain identical - only audio and messaging changes
#### Legacy Sound Override System
- **Custom ambience:** Place in `sounds/[Pack Name]/ambience/`
- **Custom enemy sounds:** Place in `sounds/[Pack Name]/`
- **Custom footsteps:** Reference in level JSON as `"footstep_sound"`
- **Ending scene:** Add `end.ogg` in the level pack directory
This system allows complete audio customization. For example, skull storms could become firestorms just by replacing the skull storm sounds in your pack's sound directory.
This legacy system allows complete audio customization through file replacement. For example, skull storms could become firestorms just by replacing the skull storm sounds in your pack's sound directory.
## Complete Example Level
+2 -1
View File
@@ -73,13 +73,14 @@ class Catapult(Object):
self.launchDelay = 900 # Time between launch sound and pumpkin firing
self.pendingPumpkin = None # Store pending pumpkin data
self.pumpkinLaunchTime = 0 # When to launch the pending pumpkin
self.launchSound = "catapult_launch" # Configurable launch sound
def fire(self, currentTime, player):
"""Start the firing sequence"""
self.lastFireTime = currentTime
# Play launch sound using directional audio
play_directional_sound(self.sounds, "catapult_launch", player.xPos, self.xPos)
play_directional_sound(self.sounds, self.launchSound, player.xPos, self.xPos)
# Set up pending pumpkin
isHigh = random.choice([True, False])
+2 -2
View File
@@ -261,8 +261,8 @@ class Enemy(Object):
# Handle witch-specific drops
if self.enemyType == "witch":
# Determine which item to drop
hasBroom = any(weapon.name == "witch_broom" for weapon in self.level.player.weapons)
hasNunchucks = any(weapon.name == "nunchucks" for weapon in self.level.player.weapons)
hasBroom = any(weapon.originalName == "witch_broom" for weapon in self.level.player.weapons)
hasNunchucks = any(weapon.originalName == "nunchucks" for weapon in self.level.player.weapons)
# Drop witch_broom only if player has neither broom nor nunchucks
itemType = "witch_broom" if not (hasBroom or hasNunchucks) else "cauldron"
+6 -2
View File
@@ -21,6 +21,10 @@ class GraspingHands(Object):
self.delay = delay # Delay in milliseconds before ground starts crumbling
self.crumble_speed = crumble_speed # How fast the crumbling catches up (tiles per frame)
# Default messages (can be overridden by sound override system)
self.warningMessage = "The ground crumbles as the dead reach for you."
self.deathMessage = "The hands of the dead drag you down!"
# State tracking
self.isTriggered = False # Has the player entered the zone?
self.triggerTime = 0 # When did the player enter the zone?
@@ -53,7 +57,7 @@ class GraspingHands(Object):
# Play initial warning sound
play_sound(self.sounds["grasping_hands_start"])
speak("The ground crumbles as the dead reach for you.")
speak(self.warningMessage)
def reset(self):
"""Reset the trap when player leaves the range"""
@@ -114,7 +118,7 @@ class GraspingHands(Object):
obj_stop(self.crumbleChannel)
self.crumbleChannel = None
speak("The hands of the dead drag you down!")
speak(self.deathMessage)
player.set_health(0)
return True
# Player is invincible - no warning needed
+2 -2
View File
@@ -30,7 +30,7 @@ class GraveObject(Object):
player.isDucking
and not player.isRunning
and player.currentWeapon
and player.currentWeapon.name == "rusty_shovel"
and player.currentWeapon.originalName == "rusty_shovel"
):
self.isCollected = True # Mark as collected when collection succeeds
return True
@@ -52,7 +52,7 @@ class GraveObject(Object):
player.isDucking
and not player.isRunning
and player.currentWeapon
and player.currentWeapon.name == "rusty_shovel"
and player.currentWeapon.originalName == "rusty_shovel"
)
def fill_grave(self, player):
+80 -8
View File
@@ -41,12 +41,23 @@ class Level:
# Pass footstep sound to player
self.player.set_footstep_sound(self.footstepSound)
# Apply weapon sound overrides if specified
if "weapon_sound_overrides" in levelData:
self._apply_weapon_overrides(levelData["weapon_sound_overrides"])
# Level intro message (skip for survival mode)
if levelData["level_id"] != 999: # 999 is survival mode
# Check if level uses new dialog format or old description format
if "dialog" in levelData:
# Use new dialog system with sound support
messagebox(levelData["dialog"], self.sounds)
# After dialogue, show standard level description if present
if "description" in levelData:
levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. "
if self.isLocked:
levelIntro += "This is a boss level. You must defeat all enemies before you can advance. "
levelIntro += levelData["description"]
messagebox(levelIntro)
elif "description" in levelData:
# Use traditional description format
levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. "
@@ -106,6 +117,9 @@ class Level:
fireInterval=obj.get("fireInterval", 5000),
firingRange=obj.get("range", 20),
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(catapult, obj["sound_overrides"])
self.objects.append(catapult)
# Check if this is grasping hands
elif obj.get("type") == "grasping_hands":
@@ -116,6 +130,9 @@ class Level:
delay=obj.get("delay", 1000),
crumble_speed=obj.get("crumble_speed", 0.03),
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(graspingHands, obj["sound_overrides"])
self.objects.append(graspingHands)
# Check if this is a grave
elif obj.get("type") == "grave":
@@ -126,6 +143,9 @@ class Level:
item=obj.get("item", None),
zombieSpawnChance=obj.get("zombie_spawn_chance", 0),
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(grave, obj["sound_overrides"])
self.objects.append(grave)
# Check if this is a skull storm
elif obj.get("type") == "skull_storm":
@@ -138,6 +158,9 @@ class Level:
obj.get("frequency", {}).get("min", 2),
obj.get("frequency", {}).get("max", 5),
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(skullStorm, obj["sound_overrides"])
self.objects.append(skullStorm)
# Check if this is a coffin
elif obj.get("type") == "coffin":
@@ -154,7 +177,7 @@ class Level:
# Check distance from graves
isValidPosition = True
for existingObj in self.objects:
if existingObj.soundName == "grave" and not hasattr(existingObj, "graveItem"):
if existingObj.originalSoundName == "grave" and not hasattr(existingObj, "graveItem"):
distance = abs(obj["x"] - existingObj.xPos)
if distance < 3:
isValidPosition = False
@@ -203,6 +226,9 @@ class Level:
isHazard=obj.get("hazard", False),
zombieSpawnChance=obj.get("zombieSpawnChance", 0),
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(gameObject, obj["sound_overrides"])
self.objects.append(gameObject)
enemyCount = len(self.enemies)
coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken"))
@@ -219,7 +245,7 @@ class Level:
continue
# Check for potential zombie spawn from graves
if obj.soundName == "grave" and obj.zombieSpawnChance > 0 and not obj.hasSpawned:
if obj.originalSoundName == "grave" and obj.zombieSpawnChance > 0 and not obj.hasSpawned:
distance = abs(self.player.xPos - obj.xPos)
if distance < 6: # Within 6 tiles
@@ -371,7 +397,7 @@ class Level:
# Handle grave edge warnings
if (
obj.isHazard and obj.soundName != "spiderweb" and not isinstance(obj, GraspingHands)
obj.isHazard and obj.originalSoundName != "spiderweb" and not isinstance(obj, GraspingHands)
): # Exclude spiderwebs and grasping hands
distance = abs(self.player.xPos - obj.xPos)
currentTime = pygame.time.get_ticks()
@@ -396,9 +422,9 @@ class Level:
if currentPos not in obj.collectedPositions:
play_sound(self.sounds[f"get_{obj.soundName}"])
obj.collect_at_position(currentPos)
self.player.collectedItems.append(obj.soundName)
self.player.collectedItems.append(obj.originalSoundName)
self.player.stats.update_stat("Items collected", 1)
if obj.soundName == "bone_dust":
if obj.originalSoundName == "bone_dust":
self.player._coins += 1
self.player.add_save_bone_dust(1) # Add to save bone dust counter too
self.levelScore += 100
@@ -427,7 +453,7 @@ class Level:
continue
# Handle spiderweb - this should trigger for both walking and jumping if not ducking
if obj.soundName == "spiderweb" and not self.player.isDucking:
if obj.originalSoundName == "spiderweb" and not self.player.isDucking:
# Create and apply web effect
webEffect = PowerUp(
obj.xPos, obj.yPos, "spiderweb", self.sounds, 0 # No direction needed since it's just for effect
@@ -449,7 +475,11 @@ class Level:
if can_collect:
# Successfully collected item while ducking with shovel
play_sound(self.sounds[f"get_{obj.graveItem}"])
# Check for item sound override
item_sound = obj.graveItem
if hasattr(obj, 'itemSoundOverride'):
item_sound = obj.itemSoundOverride
play_sound(self.sounds[f"get_{item_sound}"])
play_sound(self.sounds.get("fill_in_grave", "shovel_dig")) # Also play fill sound
self.player.stats.update_stat("Items collected", 1)
# Create PowerUp to handle the item effect
@@ -492,7 +522,7 @@ class Level:
# Check for level completion - takes precedence over everything except death
if self.player.get_health() > 0:
for obj in self.objects:
if obj.soundName == "end_of_level":
if obj.originalSoundName == "end_of_level":
# Check if player has reached or passed the end marker
if self.player.xPos >= obj.xPos:
# If level is locked, check for remaining enemies
@@ -541,3 +571,45 @@ class Level:
self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"]))
# Play throw sound
play_sound(self.sounds["throw_jack_o_lantern"])
def _apply_weapon_overrides(self, weaponOverrides):
"""Apply sound and name overrides to player weapons based on level pack theme."""
for weapon in self.player.weapons:
if weapon.name in weaponOverrides:
overrides = weaponOverrides[weapon.name]
# Override weapon name if specified
if "name" in overrides:
weapon.name = overrides["name"]
# Override attack sound if specified
if "attack_sound" in overrides:
weapon.attackSound = overrides["attack_sound"]
# Override hit sound if specified
if "hit_sound" in overrides:
weapon.hitSound = overrides["hit_sound"]
def _apply_object_sound_overrides(self, obj, soundOverrides):
"""Apply sound overrides to an object based on level pack theme."""
# Override base sound name if specified (originalSoundName remains unchanged for game logic)
if "base" in soundOverrides:
obj.soundName = soundOverrides["base"]
# Handle special object-specific overrides
# For catapults, check for launch sound override
if hasattr(obj, 'launchSound') and "launch" in soundOverrides:
obj.launchSound = soundOverrides["launch"]
# For grasping hands, check for message overrides
if hasattr(obj, 'warningMessage') and "warning_message" in soundOverrides:
obj.warningMessage = soundOverrides["warning_message"]
if hasattr(obj, 'deathMessage') and "death_message" in soundOverrides:
obj.deathMessage = soundOverrides["death_message"]
# Handle item sound overrides for graves
if hasattr(obj, 'graveItem') and obj.graveItem and "item" in soundOverrides:
# This would need to be handled in the item collection logic
# Store the override for later use
if not hasattr(obj, 'itemSoundOverride'):
obj.itemSoundOverride = soundOverrides["item"]
+1
View File
@@ -9,6 +9,7 @@ class Object:
self.xRange = [x, x] if isinstance(x, (int, float)) else x
self.yPos = yPos
self.soundName = soundName
self.originalSoundName = soundName # Store original sound name for game logic checks
self.isStatic = isStatic
self.isCollectible = isCollectible
self.isHazard = isHazard
+1 -1
View File
@@ -289,7 +289,7 @@ class Player:
# Find the weapon in player's inventory
for weapon in self.weapons:
if weapon.name == targetWeaponName:
if weapon.originalName == targetWeaponName:
self.equip_weapon(weapon)
speak(weapon.name.replace("_", " "))
return True
+27 -10
View File
@@ -54,17 +54,20 @@ class PowerUp(Object):
def apply_effect(self, player, level=None):
"""Apply the item's effect when collected"""
if self.item_type == "hand_of_glory":
# Map themed items to their original equivalents for game logic
original_item_type = self._get_original_item_type(self.item_type)
if original_item_type == "hand_of_glory":
player.start_invincibility()
elif self.item_type == "cauldron":
elif original_item_type == "cauldron":
player.restore_health()
elif self.item_type == "guts":
elif original_item_type == "guts":
player.add_guts()
player.collectedItems.append("guts")
player.collectedItems.append(original_item_type)
self.check_for_nunchucks(player)
elif self.item_type == "jack_o_lantern":
elif original_item_type == "jack_o_lantern":
player.add_jack_o_lantern()
elif self.item_type == "extra_life":
elif original_item_type == "extra_life":
# Don't give extra lives in survival mode
if level and level.levelId == 999:
# In survival mode, give bonus score instead
@@ -73,7 +76,7 @@ class PowerUp(Object):
play_sound(self.sounds.get("survivor_bonus", "get_extra_life")) # Use survivor_bonus sound if available
else:
player.extra_life()
elif self.item_type == "shin_bone": # Add shin bone handling
elif original_item_type == "shin_bone": # Add shin bone handling
player.shinBoneCount += 1
player._coins += 5
player.add_save_bone_dust(5) # Add to save bone dust counter too
@@ -99,11 +102,11 @@ class PowerUp(Object):
) # Use survivor_bonus sound if available, fallback to bone_dust
self.check_for_nunchucks(player)
elif self.item_type == "witch_broom":
elif original_item_type == "witch_broom":
broomWeapon = Weapon.create_witch_broom()
player.add_weapon(broomWeapon)
player.equip_weapon(broomWeapon)
elif self.item_type == "spiderweb":
elif original_item_type == "spiderweb":
# Bounce player back (happens even if invincible)
player.xPos -= 3 if player.xPos > self.xPos else -3
@@ -133,7 +136,7 @@ class PowerUp(Object):
if (
player.shinBoneCount >= 2
and "guts" in player.collectedItems
and not any(weapon.name == "nunchucks" for weapon in player.weapons)
and not any(weapon.originalName == "nunchucks" for weapon in player.weapons)
):
nunchucksWeapon = Weapon.create_nunchucks()
player.add_weapon(nunchucksWeapon)
@@ -143,3 +146,17 @@ class PowerUp(Object):
player.scoreboard.increase_score(basePoints + rangeModifier)
play_sound(self.sounds["get_nunchucks"])
player.stats.update_stat("Items collected", 1)
def _get_original_item_type(self, item_type):
"""Map themed item names to their original equivalents for game logic."""
# Define themed equivalents that should behave like original items
themed_mappings = {
# Christmas theme
"candy_cane": "shin_bone",
"reindeer_guts": "guts",
# Future themes can be added here
# "ice_crystal": "shin_bone",
# "frozen_heart": "guts",
}
return themed_mappings.get(item_type, item_type)
+8 -2
View File
@@ -97,6 +97,7 @@ class SaveManager:
serialized.append(
{
"name": weapon.name,
"originalName": getattr(weapon, "originalName", weapon.name),
"damage": weapon.damage,
"range": weapon.range,
"attackSound": weapon.attackSound,
@@ -120,13 +121,16 @@ class SaveManager:
jumpDurationBonus = data.get("jumpDurationBonus", 1.0)
cooldown = data.get("cooldown", 500) # Default cooldown for old saves
# Get originalName (for backward compatibility with old saves)
originalName = data.get("originalName", data["name"])
# For old saves, restore proper bonuses and cooldowns for specific weapons
if data["name"] == "witch_broom" and speedBonus == 1.0:
if originalName == "witch_broom" and speedBonus == 1.0:
speedBonus = 1.17
jumpDurationBonus = 1.25
# Restore proper cooldown for nunchucks in old saves
if data["name"] == "nunchucks" and cooldown == 500:
if originalName == "nunchucks" and cooldown == 500:
cooldown = 250
weapon = Weapon(
@@ -140,6 +144,8 @@ class SaveManager:
speedBonus=speedBonus,
jumpDurationBonus=jumpDurationBonus,
)
# Set originalName after creation for backward compatibility
weapon.originalName = originalName
weapons.append(weapon)
return weapons
+1
View File
@@ -15,6 +15,7 @@ class Weapon:
jumpDurationBonus=1.0,
):
self.name = name
self.originalName = name # Store original name for game logic checks
self.damage = damage
self.range = range # Range in tiles
self.attackSound = attackSound
+85 -12
View File
@@ -525,7 +525,7 @@ class WickedQuest:
continue
if self.currentGame:
# Ask player to choose game mode
mode_choice = game_mode_menu(self.get_sounds())
mode_choice = game_mode_menu(self.get_sounds(), self.currentGame)
if mode_choice == "story":
self.player = None # Reset player for new game
self.gameStartTime = pygame.time.get_ticks()
@@ -686,23 +686,96 @@ class WickedQuest:
self.currentLevel = Level(levelData, self.get_sounds(), self.player, self.currentGame)
def game_mode_menu(sounds):
def game_mode_menu(sounds, game_dir=None):
"""Display game mode selection menu using instruction_menu.
Args:
sounds (dict): Dictionary of loaded sound effects
game_dir (str): Current game directory to check for instructions/credits
Returns:
str: Selected game mode or None if cancelled
"""
choice = instruction_menu(sounds, "Select game mode:", "Story", "Survival Mode")
if choice == "Story":
return "story"
elif choice == "Survival Mode":
return "survival"
else:
return None
from src.game_selection import get_game_dir_path
import os
# Build base menu options
menu_options = ["Story", "Survival Mode"]
# Check for level pack specific files if game directory is provided
if game_dir:
try:
game_path = get_game_dir_path(game_dir)
# Check for instructions.txt
instructions_path = os.path.join(game_path, "instructions.txt")
if os.path.exists(instructions_path):
menu_options.append("Instructions")
# Check for credits.txt
credits_path = os.path.join(game_path, "credits.txt")
if os.path.exists(credits_path):
menu_options.append("Credits")
except Exception:
# If there's any error checking files, just continue with basic menu
pass
while True:
choice = instruction_menu(sounds, "Select game mode:", *menu_options)
if choice == "Story":
return "story"
elif choice == "Survival Mode":
return "survival"
elif choice == "Instructions" and game_dir:
# Display instructions file
try:
game_path = get_game_dir_path(game_dir)
instructions_path = os.path.join(game_path, "instructions.txt")
print(f"DEBUG: Looking for instructions at: {instructions_path}")
if os.path.exists(instructions_path):
print("DEBUG: Instructions file found, loading content...")
with open(instructions_path, 'r', encoding='utf-8') as f:
instructions_content = f.read()
print(f"DEBUG: Content length: {len(instructions_content)} characters")
print("DEBUG: Calling display_text...")
# Convert string to list of lines for display_text
content_lines = instructions_content.split('\n')
print(f"DEBUG: Split into {len(content_lines)} lines")
display_text(content_lines)
print("DEBUG: display_text returned")
else:
print("DEBUG: Instructions file not found at expected path")
speak("Instructions file not found")
except Exception as e:
print(f"DEBUG: Error loading instructions: {str(e)}")
speak(f"Error loading instructions: {str(e)}")
elif choice == "Credits" and game_dir:
# Display credits file
try:
game_path = get_game_dir_path(game_dir)
credits_path = os.path.join(game_path, "credits.txt")
print(f"DEBUG: Looking for credits at: {credits_path}")
if os.path.exists(credits_path):
print("DEBUG: Credits file found, loading content...")
with open(credits_path, 'r', encoding='utf-8') as f:
credits_content = f.read()
print(f"DEBUG: Content length: {len(credits_content)} characters")
print("DEBUG: Calling display_text...")
# Convert string to list of lines for display_text
content_lines = credits_content.split('\n')
print(f"DEBUG: Split into {len(content_lines)} lines")
display_text(content_lines)
print("DEBUG: display_text returned")
else:
print("DEBUG: Credits file not found at expected path")
speak("Credits file not found")
except Exception as e:
print(f"DEBUG: Error loading credits: {str(e)}")
speak(f"Error loading credits: {str(e)}")
else:
return None
if __name__ == "__main__":