Update keybindings and enhance loading screen functionality
- Refactor keybindings for gas spawning across multiple configurations - Implement new loading screen updates during game initialization - Add tests to ensure all weapon actions are exposed in keybinding profiles - Introduce new assets for explosion effects
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 419 B |
Binary file not shown.
|
After Width: | Height: | Size: 425 B |
Binary file not shown.
|
After Width: | Height: | Size: 428 B |
Binary file not shown.
|
After Width: | Height: | Size: 416 B |
@@ -14,7 +14,7 @@
|
|||||||
"keydown_Space": "spawn_new_bomb",
|
"keydown_Space": "spawn_new_bomb",
|
||||||
"keydown_N": "spawn_new_nuclear_bomb",
|
"keydown_N": "spawn_new_nuclear_bomb",
|
||||||
"keydown_Left_Ctrl": "spawn_new_mine",
|
"keydown_Left_Ctrl": "spawn_new_mine",
|
||||||
"keydown_G": "spawn_gas",
|
"keydown_G": "spawn_new_gas",
|
||||||
"keydown_P": "toggle_pause"
|
"keydown_P": "toggle_pause"
|
||||||
},
|
},
|
||||||
"keybinding_start_menu": {
|
"keybinding_start_menu": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
keybinding_game:
|
keybinding_game:
|
||||||
controllerbuttondown_a: spawn_rat
|
controllerbuttondown_a: spawn_new_mine
|
||||||
|
controllerbuttondown_b: spawn_rat
|
||||||
controllerbuttondown_dpad_up: start_scrolling|Up
|
controllerbuttondown_dpad_up: start_scrolling|Up
|
||||||
controllerbuttondown_dpad_down: start_scrolling|Down
|
controllerbuttondown_dpad_down: start_scrolling|Down
|
||||||
controllerbuttondown_dpad_left: start_scrolling|Left
|
controllerbuttondown_dpad_left: start_scrolling|Left
|
||||||
@@ -9,9 +10,9 @@ keybinding_game:
|
|||||||
controllerbuttonup_dpad_left: stop_scrolling
|
controllerbuttonup_dpad_left: stop_scrolling
|
||||||
controllerbuttonup_dpad_right: stop_scrolling
|
controllerbuttonup_dpad_right: stop_scrolling
|
||||||
controllerbuttondown_x: spawn_new_bomb
|
controllerbuttondown_x: spawn_new_bomb
|
||||||
controllerbuttondown_y: spawn_new_nuclear_bomb
|
controllerbuttondown_y: spawn_new_gas
|
||||||
controllerbuttondown_leftshoulder: spawn_new_mine
|
controllerbuttondown_guide: spawn_new_nuclear_bomb
|
||||||
controllerbuttondown_rightshoulder: spawn_gas
|
controllerbuttondown_misc1: spawn_new_nuclear_bomb
|
||||||
controllerbuttondown_start: toggle_pause
|
controllerbuttondown_start: toggle_pause
|
||||||
|
|
||||||
keybinding_start_menu:
|
keybinding_start_menu:
|
||||||
@@ -21,15 +22,13 @@ keybinding_start_menu:
|
|||||||
controllerbuttondown_dpad_down: menu_down
|
controllerbuttondown_dpad_down: menu_down
|
||||||
controllerbuttondown_dpad_left: menu_left
|
controllerbuttondown_dpad_left: menu_left
|
||||||
controllerbuttondown_dpad_right: menu_right
|
controllerbuttondown_dpad_right: menu_right
|
||||||
controllerbuttondown_b: quit_game
|
controllerbuttondown_misc1: quit_game
|
||||||
controllerbuttondown_back: quit_game
|
|
||||||
|
|
||||||
keybinding_paused:
|
keybinding_paused:
|
||||||
controllerbuttondown_a: reset_game
|
|
||||||
controllerbuttondown_start: toggle_pause
|
controllerbuttondown_start: toggle_pause
|
||||||
controllerbuttondown_dpad_up: menu_up
|
controllerbuttondown_dpad_up: menu_up
|
||||||
controllerbuttondown_dpad_down: menu_down
|
controllerbuttondown_dpad_down: menu_down
|
||||||
controllerbuttondown_dpad_left: menu_left
|
controllerbuttondown_dpad_left: menu_left
|
||||||
controllerbuttondown_dpad_right: menu_right
|
controllerbuttondown_dpad_right: menu_right
|
||||||
controllerbuttondown_b: quit_game
|
controllerbuttondown_misc1: quit_game
|
||||||
controllerbuttondown_back: quit_game
|
controllerbuttondown_guide: quit_game
|
||||||
@@ -13,6 +13,7 @@ keybinding_game:
|
|||||||
keydown_Space: spawn_new_bomb
|
keydown_Space: spawn_new_bomb
|
||||||
keydown_N: spawn_new_nuclear_bomb
|
keydown_N: spawn_new_nuclear_bomb
|
||||||
keydown_Left_Ctrl: spawn_new_mine
|
keydown_Left_Ctrl: spawn_new_mine
|
||||||
|
keydown_G: spawn_new_gas
|
||||||
keydown_P: toggle_pause
|
keydown_P: toggle_pause
|
||||||
|
|
||||||
keybinding_start_menu:
|
keybinding_start_menu:
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ keybinding_game:
|
|||||||
joybuttondown_1: spawn_new_bomb
|
joybuttondown_1: spawn_new_bomb
|
||||||
joybuttondown_3: spawn_new_nuclear_bomb
|
joybuttondown_3: spawn_new_nuclear_bomb
|
||||||
joybuttondown_2: spawn_new_mine
|
joybuttondown_2: spawn_new_mine
|
||||||
|
joybuttondown_4: spawn_new_gas
|
||||||
|
joybuttondown_5: spawn_new_gas
|
||||||
joybuttondown_16: toggle_pause
|
joybuttondown_16: toggle_pause
|
||||||
|
|
||||||
keybinding_start_menu:
|
keybinding_start_menu:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ keybinding_game:
|
|||||||
joybuttondown_11: spawn_new_nuclear_bomb
|
joybuttondown_11: spawn_new_nuclear_bomb
|
||||||
joybuttondown_4: spawn_new_mine
|
joybuttondown_4: spawn_new_mine
|
||||||
joybuttondown_10: toggle_pause
|
joybuttondown_10: toggle_pause
|
||||||
joybuttondown_5: spawn_gas
|
joybuttondown_5: spawn_new_gas
|
||||||
|
|
||||||
keybinding_start_menu:
|
keybinding_start_menu:
|
||||||
joybuttondown_9: reset_game
|
joybuttondown_9: reset_game
|
||||||
|
|||||||
+16
-1
@@ -278,6 +278,9 @@ class KeyBindings:
|
|||||||
def spawn_new_nuclear_bomb(self):
|
def spawn_new_nuclear_bomb(self):
|
||||||
self.spawn_nuclear_bomb(self.pointer)
|
self.spawn_nuclear_bomb(self.pointer)
|
||||||
|
|
||||||
|
def spawn_new_gas(self):
|
||||||
|
self.spawn_gas()
|
||||||
|
|
||||||
def toggle_audio(self):
|
def toggle_audio(self):
|
||||||
self.render_engine.audio = not self.render_engine.audio
|
self.render_engine.audio = not self.render_engine.audio
|
||||||
self.audio = self.render_engine.audio
|
self.audio = self.render_engine.audio
|
||||||
@@ -287,7 +290,19 @@ class KeyBindings:
|
|||||||
self.render_engine.stop_sound()
|
self.render_engine.stop_sound()
|
||||||
|
|
||||||
def toggle_pause(self):
|
def toggle_pause(self):
|
||||||
self.game_status = "paused" if self.game_status == "game" else "game"
|
if getattr(self, "game_end", (False, None))[0]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.game_status == "game":
|
||||||
|
self.game_status = "paused"
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.game_status == "paused":
|
||||||
|
self.game_status = "game"
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.game_status == "start_menu" and getattr(self, "menu_screen", None) == "start":
|
||||||
|
self.reset_game()
|
||||||
|
|
||||||
def toggle_full_screen(self):
|
def toggle_full_screen(self):
|
||||||
self.full_screen = not self.full_screen
|
self.full_screen = not self.full_screen
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ class Graphics():
|
|||||||
def load_assets(self):
|
def load_assets(self):
|
||||||
theme_index = self.get_theme_index()
|
theme_index = self.get_theme_index()
|
||||||
print(f"[gfx] load_assets requested: level={self.current_level + 1} theme={theme_index}")
|
print(f"[gfx] load_assets requested: level={self.current_level + 1} theme={theme_index}")
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self._update_startup_loading(
|
||||||
|
"Loading graphics",
|
||||||
|
detail=f"Preparing theme {theme_index}",
|
||||||
|
progress=0.3,
|
||||||
|
)
|
||||||
if not hasattr(self, "theme_assets_cache"):
|
if not hasattr(self, "theme_assets_cache"):
|
||||||
self.theme_assets_cache = {}
|
self.theme_assets_cache = {}
|
||||||
if not hasattr(self, "blood_layer_sprites"):
|
if not hasattr(self, "blood_layer_sprites"):
|
||||||
@@ -18,6 +24,12 @@ class Graphics():
|
|||||||
|
|
||||||
if not getattr(self, "common_assets_loaded", False):
|
if not getattr(self, "common_assets_loaded", False):
|
||||||
print("Loading graphics assets...")
|
print("Loading graphics assets...")
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self._update_startup_loading(
|
||||||
|
"Loading graphics",
|
||||||
|
detail="Decoding sprites and tiles",
|
||||||
|
progress=0.4,
|
||||||
|
)
|
||||||
self.rat_assets = {}
|
self.rat_assets = {}
|
||||||
self.rat_assets_textures = {}
|
self.rat_assets_textures = {}
|
||||||
self.rat_image_sizes = {}
|
self.rat_image_sizes = {}
|
||||||
@@ -56,6 +68,12 @@ class Graphics():
|
|||||||
)
|
)
|
||||||
|
|
||||||
print("Pre-generating blood stain pool...")
|
print("Pre-generating blood stain pool...")
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self._update_startup_loading(
|
||||||
|
"Loading graphics",
|
||||||
|
detail="Generating blood pool",
|
||||||
|
progress=0.58,
|
||||||
|
)
|
||||||
self.blood_stain_textures = []
|
self.blood_stain_textures = []
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
blood_surface = self.render_engine.generate_blood_surface()
|
blood_surface = self.render_engine.generate_blood_surface()
|
||||||
@@ -70,6 +88,12 @@ class Graphics():
|
|||||||
|
|
||||||
if theme_index not in self.theme_assets_cache:
|
if theme_index not in self.theme_assets_cache:
|
||||||
print(f"Loading theme assets {theme_index}...")
|
print(f"Loading theme assets {theme_index}...")
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self._update_startup_loading(
|
||||||
|
"Loading graphics",
|
||||||
|
detail=f"Loading theme {theme_index} art",
|
||||||
|
progress=0.74,
|
||||||
|
)
|
||||||
self.theme_assets_cache[theme_index] = {
|
self.theme_assets_cache[theme_index] = {
|
||||||
"floor_tile": self.render_engine.create_color_surface((128, 128, 128)),
|
"floor_tile": self.render_engine.create_color_surface((128, 128, 128)),
|
||||||
"tunnel": self.render_engine.load_image("Rat/BMP_TUNNEL.png"),
|
"tunnel": self.render_engine.load_image("Rat/BMP_TUNNEL.png"),
|
||||||
@@ -122,6 +146,13 @@ class Graphics():
|
|||||||
else:
|
else:
|
||||||
print(f"[gfx] theme cache hit -> reusing theme {theme_index}")
|
print(f"[gfx] theme cache hit -> reusing theme {theme_index}")
|
||||||
|
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self._update_startup_loading(
|
||||||
|
"Loading graphics",
|
||||||
|
detail="Finishing render setup",
|
||||||
|
progress=0.84,
|
||||||
|
)
|
||||||
|
|
||||||
self.loaded_theme_index = theme_index
|
self.loaded_theme_index = theme_index
|
||||||
theme_assets = self.theme_assets_cache[theme_index]
|
theme_assets = self.theme_assets_cache[theme_index]
|
||||||
self.floor_tile = theme_assets["floor_tile"]
|
self.floor_tile = theme_assets["floor_tile"]
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ CONTROLLER_BUTTON_NAMES = {
|
|||||||
sdl2.SDL_CONTROLLER_BUTTON_DPAD_RIGHT: "dpad_right",
|
sdl2.SDL_CONTROLLER_BUTTON_DPAD_RIGHT: "dpad_right",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasattr(sdl2, "SDL_CONTROLLER_BUTTON_MISC1"):
|
||||||
|
CONTROLLER_BUTTON_NAMES[sdl2.SDL_CONTROLLER_BUTTON_MISC1] = "misc1"
|
||||||
|
|
||||||
|
|
||||||
def _decode_sdl_string(value):
|
def _decode_sdl_string(value):
|
||||||
if not value:
|
if not value:
|
||||||
@@ -93,6 +96,8 @@ class GameWindow:
|
|||||||
# Font system
|
# Font system
|
||||||
self.fonts = self.generate_fonts("assets/decterm.ttf")
|
self.fonts = self.generate_fonts("assets/decterm.ttf")
|
||||||
|
|
||||||
|
self.show_loading_screen("Loading Mice!", detail="Initializing renderer", progress=0.02)
|
||||||
|
|
||||||
# Initial loading dialog
|
# Initial loading dialog
|
||||||
# self.dialog("Loading assets...")
|
# self.dialog("Loading assets...")
|
||||||
# self.renderer.present()
|
# self.renderer.present()
|
||||||
@@ -377,6 +382,54 @@ class GameWindow:
|
|||||||
else:
|
else:
|
||||||
self.renderer.draw_rect((x, y, width, height), sdl2.ext.Color(*outline))
|
self.renderer.draw_rect((x, y, width, height), sdl2.ext.Color(*outline))
|
||||||
|
|
||||||
|
def show_loading_screen(self, message="Loading Mice!", detail=None, progress=None):
|
||||||
|
"""Render a simple loading splash while startup work is in progress."""
|
||||||
|
self.window.show()
|
||||||
|
|
||||||
|
target_width, target_height = self.target_size
|
||||||
|
panel_width = min(720, max(420, target_width - 120))
|
||||||
|
panel_height = 230 if detail or progress is not None else 190
|
||||||
|
panel_x = (target_width - panel_width) // 2
|
||||||
|
panel_y = (target_height - panel_height) // 2
|
||||||
|
|
||||||
|
clamped_progress = None
|
||||||
|
if progress is not None:
|
||||||
|
clamped_progress = max(0.0, min(1.0, float(progress)))
|
||||||
|
|
||||||
|
self.renderer.clear()
|
||||||
|
self.draw_rectangle(0, 0, target_width, target_height, "loading-bg", filling=(10, 12, 18))
|
||||||
|
self.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "loading-panel", filling=(24, 29, 38))
|
||||||
|
self.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "loading-panel-outline", outline=(90, 98, 112))
|
||||||
|
self.draw_rectangle(panel_x, panel_y, panel_width, 6, "loading-panel-accent", filling=(241, 196, 92))
|
||||||
|
|
||||||
|
self.draw_text("MICE!", self.fonts[52], ("center", panel_y + 28), (240, 240, 240))
|
||||||
|
self.draw_text(message, self.fonts[28], ("center", panel_y + 102), (241, 196, 92))
|
||||||
|
|
||||||
|
if detail:
|
||||||
|
self.draw_text(detail, self.fonts[18], ("center", panel_y + 146), (208, 212, 218))
|
||||||
|
|
||||||
|
if clamped_progress is not None:
|
||||||
|
bar_width = panel_width - 80
|
||||||
|
bar_height = 18
|
||||||
|
bar_x = panel_x + 40
|
||||||
|
bar_y = panel_y + panel_height - 48
|
||||||
|
self.draw_rectangle(bar_x, bar_y, bar_width, bar_height, "loading-bar-track", filling=(54, 60, 72))
|
||||||
|
self.draw_rectangle(bar_x, bar_y, bar_width, bar_height, "loading-bar-outline", outline=(90, 98, 112))
|
||||||
|
|
||||||
|
fill_width = int((bar_width - 4) * clamped_progress)
|
||||||
|
if fill_width > 0:
|
||||||
|
self.draw_rectangle(
|
||||||
|
bar_x + 2,
|
||||||
|
bar_y + 2,
|
||||||
|
fill_width,
|
||||||
|
bar_height - 4,
|
||||||
|
"loading-bar-fill",
|
||||||
|
filling=(241, 196, 92),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.renderer.present()
|
||||||
|
sdl2.SDL_PumpEvents()
|
||||||
|
|
||||||
def draw_pointer(self, x, y):
|
def draw_pointer(self, x, y):
|
||||||
"""Draw a red pointer rectangle at specified coordinates"""
|
"""Draw a red pointer rectangle at specified coordinates"""
|
||||||
x = x + self.w_offset
|
x = x + self.w_offset
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
[mice-launcher] script_dir=/home/enne2/dev/mice
|
||||||
|
[mice-launcher] game_dir=/home/enne2/dev/mice
|
||||||
|
[mice-launcher] python=/home/enne2/dev/mice/.venv/bin/python
|
||||||
|
Game starting...
|
||||||
|
Loading map from /home/enne2/dev/mice/assets/Rat/level.dat (level 0)
|
||||||
|
Loaded profile: Player1
|
||||||
|
Screen size: 1280x1280
|
||||||
|
[input] no joystick detected
|
||||||
|
[input] keybindings profile=pc source=keybindings_pc.yaml reason=default
|
||||||
|
[gfx] load_assets requested: level=1 theme=1
|
||||||
|
Loading graphics assets...
|
||||||
|
Pre-generating blood stain pool...
|
||||||
|
[gfx] common assets loaded
|
||||||
|
Loading theme assets 1...
|
||||||
|
[gfx] theme cache miss -> loaded theme 1
|
||||||
|
[flow] start_game: level=1 difficulty=easy starting_rats=5 rat_speed=100%
|
||||||
|
[audio] level 1 music=Clockwork_Thicket.mp3 source=config
|
||||||
|
[flow] choose_start computed 279 spawnable cells
|
||||||
|
[flow] spawn_rat using position=(27, 16)
|
||||||
|
[flow] spawn_rat using position=(30, 6)
|
||||||
|
[flow] spawn_rat using position=(10, 14)
|
||||||
|
[flow] spawn_rat using position=(1, 30)
|
||||||
|
[flow] spawn_rat using position=(20, 30)
|
||||||
|
[gfx] generating background texture for level=1 theme=1
|
||||||
|
[flow] reset_game called: game_end=(False, None) game_status=start_menu menu_screen=start points=0 level=1
|
||||||
|
[flow] reset_game -> leaving menu_screen=start and entering gameplay
|
||||||
|
[flow] load_level requested: target_level=1 preserve_points=False show_menu=False menu_screen=None current_points=0 next_theme=1
|
||||||
|
[audio] level 1 music=Clockwork_Thicket.mp3 source=config
|
||||||
|
[flow] theme unchanged: reusing theme 1
|
||||||
|
[flow] points reset for new run
|
||||||
|
[flow] spawning 5 rats for level=1 difficulty=easy
|
||||||
|
[flow] choose_start computed 279 spawnable cells
|
||||||
|
[flow] spawn_rat using position=(8, 26)
|
||||||
|
[flow] spawn_rat using position=(27, 19)
|
||||||
|
[flow] spawn_rat using position=(10, 10)
|
||||||
|
[flow] spawn_rat using position=(27, 11)
|
||||||
|
[flow] spawn_rat using position=(6, 26)
|
||||||
|
[flow] level loaded directly into gameplay: level=1 points=0
|
||||||
|
[gfx] generating background texture for level=1 theme=1
|
||||||
|
BOOM
|
||||||
@@ -90,6 +90,10 @@ class MiceMaze(
|
|||||||
|
|
||||||
# ==================== INITIALIZATION ====================
|
# ==================== INITIALIZATION ====================
|
||||||
|
|
||||||
|
def _update_startup_loading(self, message, detail=None, progress=None):
|
||||||
|
if getattr(self, "startup_loading_active", False):
|
||||||
|
self.render_engine.show_loading_screen(message, detail=detail, progress=progress)
|
||||||
|
|
||||||
def __init__(self, maze_file, level_index=0):
|
def __init__(self, maze_file, level_index=0):
|
||||||
# Initialize user profile integration
|
# Initialize user profile integration
|
||||||
self.profile_integration = UserProfileIntegration()
|
self.profile_integration = UserProfileIntegration()
|
||||||
@@ -120,6 +124,8 @@ class MiceMaze(
|
|||||||
self.cell_size, window_title,
|
self.cell_size, window_title,
|
||||||
key_callback=self.trigger)
|
key_callback=self.trigger)
|
||||||
self.render_engine.audio = self.audio
|
self.render_engine.audio = self.audio
|
||||||
|
self.startup_loading_active = True
|
||||||
|
self._update_startup_loading("Loading Mice!", detail="Applying player settings", progress=0.08)
|
||||||
|
|
||||||
# Apply profile settings
|
# Apply profile settings
|
||||||
if hasattr(self.render_engine, 'set_sound_volume'):
|
if hasattr(self.render_engine, 'set_sound_volume'):
|
||||||
@@ -130,15 +136,22 @@ class MiceMaze(
|
|||||||
self.render_engine.set_volume(self.music_volume)
|
self.render_engine.set_volume(self.music_volume)
|
||||||
self.initialize_keybindings()
|
self.initialize_keybindings()
|
||||||
|
|
||||||
|
self._update_startup_loading("Loading configuration", detail="Reading bundled settings", progress=0.14)
|
||||||
self.configs = self.get_config()
|
self.configs = self.get_config()
|
||||||
|
|
||||||
|
self._update_startup_loading("Scanning music", detail="Building soundtrack list", progress=0.2)
|
||||||
self.available_music_tracks = self._load_available_music_tracks()
|
self.available_music_tracks = self._load_available_music_tracks()
|
||||||
self.current_level_music = None
|
self.current_level_music = None
|
||||||
|
|
||||||
|
self._update_startup_loading("Loading graphics", detail="Preparing common assets", progress=0.26)
|
||||||
self.load_assets()
|
self.load_assets()
|
||||||
|
|
||||||
|
self._update_startup_loading("Loading start menu", detail="Preparing menu animation", progress=0.9)
|
||||||
self.start_menu_animation = self.render_engine.load_animation(START_MENU_ANIMATION)
|
self.start_menu_animation = self.render_engine.load_animation(START_MENU_ANIMATION)
|
||||||
self.start_menu_animation_started_at = time.monotonic()
|
self.start_menu_animation_started_at = time.monotonic()
|
||||||
self.render_engine.window.show()
|
self._update_startup_loading("Starting", detail="Opening intro screen", progress=0.98)
|
||||||
self.render_engine.show_intro(bundle_path("assets", "Rat", "intro.png"))
|
self.render_engine.show_intro(bundle_path("assets", "Rat", "intro.png"))
|
||||||
|
self.startup_loading_active = False
|
||||||
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
|
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
|
||||||
self.scroll_cursor()
|
self.scroll_cursor()
|
||||||
self.points = 0
|
self.points = 0
|
||||||
@@ -965,10 +978,10 @@ class MiceMaze(
|
|||||||
if not count_rats and not any(isinstance(unit, points.Point) for unit in self.units.values()):
|
if not count_rats and not any(isinstance(unit, points.Point) for unit in self.units.values()):
|
||||||
self.render_engine.stop_sound()
|
self.render_engine.stop_sound()
|
||||||
self.render_engine.play_sound("VICTORY.WAV")
|
self.render_engine.play_sound("VICTORY.WAV")
|
||||||
self.render_engine.play_sound("WELLDONE.WAV", tag="effects")
|
|
||||||
self.game_status = "paused"
|
self.game_status = "paused"
|
||||||
|
|
||||||
if self._is_last_dat_level():
|
if self._is_last_dat_level():
|
||||||
|
self.render_engine.play_sound("WELLDONE.WAV", tag="effects")
|
||||||
self.game_end = (True, GAME_END_RUN_COMPLETE)
|
self.game_end = (True, GAME_END_RUN_COMPLETE)
|
||||||
self._record_run_result(completed=True)
|
self._record_run_result(completed=True)
|
||||||
print(f"[flow] final DAT victory reached: points={self.points} level={self.current_level + 1}")
|
print(f"[flow] final DAT victory reached: points={self.points} level={self.current_level + 1}")
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ def build_dummy(level_index):
|
|||||||
dummy.save_score = lambda: saves.append(dummy.points)
|
dummy.save_score = lambda: saves.append(dummy.points)
|
||||||
dummy.load_level = lambda *args, **kwargs: load_calls.append((args, kwargs))
|
dummy.load_level = lambda *args, **kwargs: load_calls.append((args, kwargs))
|
||||||
|
|
||||||
return dummy, dialogs, stats, saves, load_calls
|
return dummy, sounds, dialogs, stats, saves, load_calls
|
||||||
|
|
||||||
|
|
||||||
class FinalLevelFlowTests(unittest.TestCase):
|
class FinalLevelFlowTests(unittest.TestCase):
|
||||||
def test_debug_flag_helper_opens_final_dialog_without_recording_score(self):
|
def test_debug_flag_helper_opens_final_dialog_without_recording_score(self):
|
||||||
dummy, _, stats, saves, _ = build_dummy(3)
|
dummy, _, _, stats, saves, _ = build_dummy(3)
|
||||||
|
|
||||||
dummy.activate_debug_run_complete_dialog(score=777)
|
dummy.activate_debug_run_complete_dialog(score=777)
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertEqual(saves, [])
|
self.assertEqual(saves, [])
|
||||||
|
|
||||||
def test_run_complete_screen_uses_dedicated_music(self):
|
def test_run_complete_screen_uses_dedicated_music(self):
|
||||||
dummy, _, _, _, _ = build_dummy(3)
|
dummy, _, _, _, _, _ = build_dummy(3)
|
||||||
music_calls = []
|
music_calls = []
|
||||||
dummy.render_engine.play_music = lambda track, loop=True: music_calls.append((track, loop)) or True
|
dummy.render_engine.play_music = lambda track, loop=True: music_calls.append((track, loop)) or True
|
||||||
dummy.render_engine.pause_music = lambda: music_calls.append(("pause", False))
|
dummy.render_engine.pause_music = lambda: music_calls.append(("pause", False))
|
||||||
@@ -79,7 +79,7 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertEqual(music_calls, [(RUN_COMPLETE_MUSIC, True)])
|
self.assertEqual(music_calls, [(RUN_COMPLETE_MUSIC, True)])
|
||||||
|
|
||||||
def test_advance_level_does_not_wrap_after_last_dat_level(self):
|
def test_advance_level_does_not_wrap_after_last_dat_level(self):
|
||||||
dummy, _, stats, saves, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
dummy, _, _, stats, saves, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
||||||
|
|
||||||
dummy.advance_level()
|
dummy.advance_level()
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertEqual(load_calls, [])
|
self.assertEqual(load_calls, [])
|
||||||
|
|
||||||
def test_regular_level_clear_keeps_run_open(self):
|
def test_regular_level_clear_keeps_run_open(self):
|
||||||
dummy, _, stats, saves, _ = build_dummy(4)
|
dummy, sounds, _, stats, saves, _ = build_dummy(4)
|
||||||
|
|
||||||
result = dummy.game_over()
|
result = dummy.game_over()
|
||||||
|
|
||||||
@@ -100,9 +100,16 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}])
|
self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}])
|
||||||
self.assertEqual(stats, [])
|
self.assertEqual(stats, [])
|
||||||
self.assertEqual(saves, [])
|
self.assertEqual(saves, [])
|
||||||
|
self.assertEqual(
|
||||||
|
sounds,
|
||||||
|
[
|
||||||
|
("stop",),
|
||||||
|
(("VICTORY.WAV",), {}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_final_dat_level_records_score_and_shows_dedicated_dialog(self):
|
def test_final_dat_level_records_score_and_shows_dedicated_dialog(self):
|
||||||
dummy, dialogs, stats, saves, _ = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
dummy, sounds, dialogs, stats, saves, _ = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
||||||
|
|
||||||
result = dummy.game_over()
|
result = dummy.game_over()
|
||||||
|
|
||||||
@@ -112,6 +119,14 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertEqual(stats, [(1234, True)])
|
self.assertEqual(stats, [(1234, True)])
|
||||||
self.assertEqual(saves, [1234])
|
self.assertEqual(saves, [1234])
|
||||||
self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}])
|
self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}])
|
||||||
|
self.assertEqual(
|
||||||
|
sounds,
|
||||||
|
[
|
||||||
|
("stop",),
|
||||||
|
(("VICTORY.WAV",), {}),
|
||||||
|
(("WELLDONE.WAV",), {"tag": "effects"}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
dummy.game_over()
|
dummy.game_over()
|
||||||
|
|
||||||
@@ -121,7 +136,7 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
self.assertIs(dialogs[-1][1]["image"], dummy.assets["end"])
|
self.assertIs(dialogs[-1][1]["image"], dummy.assets["end"])
|
||||||
|
|
||||||
def test_return_after_final_dialog_goes_back_to_start_menu(self):
|
def test_return_after_final_dialog_goes_back_to_start_menu(self):
|
||||||
dummy, _, _, _, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
dummy, _, _, _, _, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1)
|
||||||
dummy.game_end = (True, GAME_END_RUN_COMPLETE)
|
dummy.game_end = (True, GAME_END_RUN_COMPLETE)
|
||||||
dummy.game_status = "paused"
|
dummy.game_status = "paused"
|
||||||
|
|
||||||
@@ -132,6 +147,35 @@ class FinalLevelFlowTests(unittest.TestCase):
|
|||||||
[((0,), {"preserve_points": False, "show_menu": True, "menu_screen": "start"})],
|
[((0,), {"preserve_points": False, "show_menu": True, "menu_screen": "start"})],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_toggle_pause_ignores_transition_screen_between_levels(self):
|
||||||
|
dummy, _, _, _, _, _ = build_dummy(2)
|
||||||
|
dummy.game_status = "start_menu"
|
||||||
|
dummy.menu_screen = "level_intro"
|
||||||
|
|
||||||
|
dummy.toggle_pause()
|
||||||
|
|
||||||
|
self.assertEqual(dummy.game_status, "start_menu")
|
||||||
|
self.assertEqual(dummy.menu_screen, "level_intro")
|
||||||
|
|
||||||
|
def test_toggle_pause_ignores_game_end_dialogs(self):
|
||||||
|
dummy, _, _, _, _, _ = build_dummy(2)
|
||||||
|
dummy.game_status = "paused"
|
||||||
|
dummy.game_end = (True, GAME_END_LEVEL_CLEAR)
|
||||||
|
|
||||||
|
dummy.toggle_pause()
|
||||||
|
|
||||||
|
self.assertEqual(dummy.game_status, "paused")
|
||||||
|
self.assertEqual(dummy.game_end, (True, GAME_END_LEVEL_CLEAR))
|
||||||
|
|
||||||
|
def test_toggle_pause_still_toggles_real_pause(self):
|
||||||
|
dummy, _, _, _, _, _ = build_dummy(2)
|
||||||
|
|
||||||
|
dummy.toggle_pause()
|
||||||
|
self.assertEqual(dummy.game_status, "paused")
|
||||||
|
|
||||||
|
dummy.toggle_pause()
|
||||||
|
self.assertEqual(dummy.game_status, "game")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from engine import controls
|
||||||
|
|
||||||
|
|
||||||
|
class DummyBindings(controls.KeyBindings):
|
||||||
|
def spawn_rat(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def toggle_audio(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def toggle_full_screen(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_scrolling(self, direction):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop_scrolling(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_new_bomb(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_new_nuclear_bomb(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_new_mine(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_new_gas(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_gas(self, parent_id=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def toggle_pause(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reset_game(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def quit_game(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def menu_up(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def menu_down(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def menu_left(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def menu_right(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class KeybindingProfileTests(unittest.TestCase):
|
||||||
|
def test_shipped_profiles_expose_all_weapon_actions(self):
|
||||||
|
dummy = DummyBindings()
|
||||||
|
conf_dir = Path(__file__).resolve().parent / "conf"
|
||||||
|
weapon_actions = {
|
||||||
|
"spawn_new_bomb",
|
||||||
|
"spawn_new_nuclear_bomb",
|
||||||
|
"spawn_new_mine",
|
||||||
|
"spawn_new_gas",
|
||||||
|
}
|
||||||
|
|
||||||
|
for config_path in sorted(conf_dir.glob("keybindings*.json")) + sorted(conf_dir.glob("keybindings*.yaml")):
|
||||||
|
bindings = controls._load_bindings_from_file(config_path)
|
||||||
|
validated = dummy._validate_bindings(bindings, config_path)
|
||||||
|
game_bindings = validated.get("keybinding_game", {})
|
||||||
|
self.assertTrue(game_bindings, f"missing keybinding_game in {config_path.name}")
|
||||||
|
|
||||||
|
actions = set(game_bindings.values())
|
||||||
|
self.assertTrue(
|
||||||
|
weapon_actions.issubset(actions),
|
||||||
|
f"incomplete weapon bindings in {config_path.name}: {sorted(actions)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user