Huge refactor of the libstormgames library. It is hopefully mostly backwards compatible. Still lots of testing to do, and probably some fixes needed, but this is a good start.
This commit is contained in:
286
menu.py
Normal file
286
menu.py
Normal file
@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Menu systems for Storm Games.
|
||||
|
||||
Provides functionality for:
|
||||
- Game menu navigation
|
||||
- Instructions display
|
||||
- Credits display
|
||||
- Sound learning interface
|
||||
- Game exit handling
|
||||
"""
|
||||
|
||||
import pygame
|
||||
import time
|
||||
import webbrowser
|
||||
from sys import exit
|
||||
from os.path import isfile
|
||||
from os import listdir
|
||||
from os.path import join
|
||||
from inspect import isfunction
|
||||
from .speech import Speech
|
||||
from .sound import adjust_master_volume, adjust_bgm_volume, adjust_sfx_volume
|
||||
from .display import display_text
|
||||
|
||||
def game_menu(sounds, *options):
|
||||
"""Display and handle the main game menu.
|
||||
|
||||
Provides menu navigation with:
|
||||
- Up/Down arrows for selection
|
||||
- Home/End for first/last option
|
||||
- Enter to select
|
||||
- Escape to exit
|
||||
- Volume controls (with Alt modifier):
|
||||
- Alt+PageUp/PageDown: Master volume up/down
|
||||
- Alt+Home/End: Background music volume up/down
|
||||
- Alt+Insert/Delete: Sound effects volume up/down
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of sound objects
|
||||
*options: Variable list of menu option names (strings)
|
||||
|
||||
Returns:
|
||||
str: Selected menu option or "exit" if user pressed escape
|
||||
"""
|
||||
# Get speech instance
|
||||
speech = Speech.get_instance()
|
||||
|
||||
loop = True
|
||||
pygame.mixer.stop()
|
||||
|
||||
if pygame.mixer.music.get_busy():
|
||||
pygame.mixer.music.unpause()
|
||||
else:
|
||||
try:
|
||||
from .sound import play_bgm
|
||||
play_bgm("sounds/music_menu.ogg")
|
||||
except:
|
||||
pass
|
||||
|
||||
currentIndex = 0
|
||||
lastSpoken = -1 # Track last spoken index
|
||||
|
||||
while loop:
|
||||
if currentIndex != lastSpoken:
|
||||
speech.speak(options[currentIndex])
|
||||
lastSpoken = currentIndex
|
||||
|
||||
event = pygame.event.wait()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
# Check for Alt modifier
|
||||
mods = pygame.key.get_mods()
|
||||
alt_pressed = mods & pygame.KMOD_ALT
|
||||
|
||||
# Volume controls (require Alt)
|
||||
if alt_pressed:
|
||||
if event.key == pygame.K_PAGEUP:
|
||||
adjust_master_volume(0.1)
|
||||
elif event.key == pygame.K_PAGEDOWN:
|
||||
adjust_master_volume(-0.1)
|
||||
elif event.key == pygame.K_HOME:
|
||||
adjust_bgm_volume(0.1)
|
||||
elif event.key == pygame.K_END:
|
||||
adjust_bgm_volume(-0.1)
|
||||
elif event.key == pygame.K_INSERT:
|
||||
adjust_sfx_volume(0.1)
|
||||
elif event.key == pygame.K_DELETE:
|
||||
adjust_sfx_volume(-0.1)
|
||||
# Regular menu navigation (no Alt required)
|
||||
else:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
exit_game()
|
||||
elif event.key == pygame.K_HOME:
|
||||
if currentIndex != 0:
|
||||
currentIndex = 0
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
if options[currentIndex] != "donate":
|
||||
pygame.mixer.music.unpause()
|
||||
elif event.key == pygame.K_END:
|
||||
if currentIndex != len(options) - 1:
|
||||
currentIndex = len(options) - 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
if options[currentIndex] != "donate":
|
||||
pygame.mixer.music.unpause()
|
||||
elif event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(options) - 1:
|
||||
currentIndex += 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
if options[currentIndex] != "donate":
|
||||
pygame.mixer.music.unpause()
|
||||
elif event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
||||
currentIndex -= 1
|
||||
try:
|
||||
sounds['menu-move'].play()
|
||||
except:
|
||||
pass
|
||||
if options[currentIndex] != "donate":
|
||||
pygame.mixer.music.unpause()
|
||||
elif event.key == pygame.K_RETURN:
|
||||
try:
|
||||
lastSpoken = -1
|
||||
try:
|
||||
sounds['menu-select'].play()
|
||||
time.sleep(sounds['menu-select'].get_length())
|
||||
except:
|
||||
pass
|
||||
eval(options[currentIndex] + "()")
|
||||
except:
|
||||
lastSpoken = -1
|
||||
pygame.mixer.music.fadeout(500)
|
||||
try:
|
||||
pygame.mixer.music.fadeout(750)
|
||||
time.sleep(1.0)
|
||||
except:
|
||||
pass
|
||||
|
||||
return options[currentIndex]
|
||||
|
||||
event = pygame.event.clear()
|
||||
time.sleep(0.001)
|
||||
|
||||
def learn_sounds(sounds):
|
||||
"""Interactive menu for learning game sounds.
|
||||
|
||||
Allows users to:
|
||||
- Navigate through available sounds
|
||||
- Play selected sounds
|
||||
- Return to menu with escape key
|
||||
|
||||
Args:
|
||||
sounds (dict): Dictionary of available sound objects
|
||||
|
||||
Returns:
|
||||
str: "menu" if user exits with escape
|
||||
"""
|
||||
# Get speech instance
|
||||
speech = Speech.get_instance()
|
||||
|
||||
loop = True
|
||||
try:
|
||||
pygame.mixer.music.pause()
|
||||
except:
|
||||
pass
|
||||
|
||||
currentIndex = 0
|
||||
|
||||
# Get list of available sounds, excluding special sounds
|
||||
soundFiles = [f for f in listdir("sounds/")
|
||||
if isfile(join("sounds/", f))
|
||||
and (f.split('.')[1].lower() in ["ogg", "wav"])
|
||||
and (f.split('.')[0].lower() not in ["game-intro", "music_menu"])
|
||||
and (not f.lower().startswith("_"))]
|
||||
|
||||
# Track last spoken index to avoid repetition
|
||||
lastSpoken = -1
|
||||
|
||||
while loop:
|
||||
if currentIndex != lastSpoken:
|
||||
speech.speak(soundFiles[currentIndex][:-4])
|
||||
lastSpoken = currentIndex
|
||||
|
||||
event = pygame.event.wait()
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
try:
|
||||
pygame.mixer.music.unpause()
|
||||
except:
|
||||
pass
|
||||
|
||||
return "menu"
|
||||
|
||||
if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(soundFiles) - 1:
|
||||
pygame.mixer.stop()
|
||||
currentIndex += 1
|
||||
|
||||
if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
|
||||
pygame.mixer.stop()
|
||||
currentIndex -= 1
|
||||
|
||||
if event.key == pygame.K_RETURN:
|
||||
try:
|
||||
soundName = soundFiles[currentIndex][:-4]
|
||||
pygame.mixer.stop()
|
||||
sounds[soundName].play()
|
||||
except:
|
||||
lastSpoken = -1
|
||||
speech.speak("Could not play sound.")
|
||||
|
||||
event = pygame.event.clear()
|
||||
time.sleep(0.001)
|
||||
|
||||
def instructions():
|
||||
"""Display game instructions from file.
|
||||
|
||||
Reads and displays instructions from 'files/instructions.txt'.
|
||||
If file is missing, displays an error message.
|
||||
"""
|
||||
try:
|
||||
pygame.mixer.music.pause()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
with open('files/instructions.txt', 'r') as f:
|
||||
info = f.readlines()
|
||||
except:
|
||||
info = ["Instructions file is missing."]
|
||||
display_text(info)
|
||||
|
||||
try:
|
||||
pygame.mixer.music.unpause()
|
||||
except:
|
||||
pass
|
||||
|
||||
def credits():
|
||||
"""Display game credits from file.
|
||||
|
||||
Reads and displays credits from 'files/credits.txt'.
|
||||
Adds game name header before displaying.
|
||||
If file is missing, displays an error message.
|
||||
"""
|
||||
try:
|
||||
pygame.mixer.music.pause()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
with open('files/credits.txt', 'r') as f:
|
||||
info = f.readlines()
|
||||
# Add the header
|
||||
from .config import gameName
|
||||
info.insert(0, gameName + ": brought to you by Storm Dragon")
|
||||
except:
|
||||
info = ["Credits file is missing."]
|
||||
display_text(info)
|
||||
|
||||
try:
|
||||
pygame.mixer.music.unpause()
|
||||
except:
|
||||
pass
|
||||
|
||||
def donate():
|
||||
"""Open the donation webpage.
|
||||
|
||||
Pauses background music and opens the Ko-fi donation page.
|
||||
"""
|
||||
pygame.mixer.music.pause()
|
||||
webbrowser.open('https://ko-fi.com/stormux')
|
||||
|
||||
def exit_game():
|
||||
"""Clean up and exit the game."""
|
||||
# Get speech instance and check provider type
|
||||
speech = Speech.get_instance()
|
||||
if speech.provider_name == "speechd":
|
||||
speech.close()
|
||||
|
||||
pygame.mixer.music.stop()
|
||||
pygame.quit()
|
||||
exit()
|
Reference in New Issue
Block a user