fix/imp-13-ddr-song-load #2

Merged
mr.two merged 1 commits from fix/imp-13-ddr-song-load into master 2026-02-16 14:15:20 +00:00
5 changed files with 184 additions and 16 deletions
Showing only changes of commit f553b09004 - Show all commits

103
GEMINI.md
View File

@@ -52,3 +52,106 @@ Based on the analysis of `impostor.lua`, the following regularities and conventi
## Agent Directives ## Agent Directives
- **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user. - **Git Operations:** In the future, do not perform `git add` or `git commit` operations. This responsibility will be handled by the user.
---
# Impostor Minigames Documentation
This document provides comprehensive documentation for all three minigames implemented in the Impostor game: Button Mash, Rhythm, and DDR (Dance Dance Revolution).
## Table of Contents
- [Overview](#overview)
- [Button Mash Minigame](#button-mash-minigame)
- [Rhythm Minigame](#rhythm-minigame)
- [DDR Minigame](#ddr-minigame)
- [Integration Guide](#integration-guide)
- [Common Features](#common-features)
---
## Overview
The Impostor game includes three interactive minigames that can be triggered during gameplay. Each minigame presents a unique challenge that the player must complete to progress. All minigames feature:
- **Overlay rendering** - Minigames render over the current game window
- **Progress tracking** - Visual indicators show completion status
- **Return mechanism** - Automatic return to the calling window upon completion
- **Visual feedback** - Button presses and hits are visually indicated
---
## Button Mash Minigame
**File**: `inc/window/window.minigame.mash.lua`
### Description
A fast-paced minigame where the player must repeatedly press the Z button to fill a progress bar. The bar automatically degrades over time, and the degradation rate increases as the bar fills up, creating increasing difficulty.
### Gameplay Mechanics
- **Objective**: Fill the progress bar to 100% by pressing Z repeatedly
- **Challenge**: The bar degrades automatically, with degradation increasing as it fills
- **Win Condition**: Bar reaches 100%
- **No Fail State**: Player can keep trying until successful
### Visual Elements
#### Simple sketch
![button_mash.png](/button_mash.png)
```
Progress Bar (200x12px at 20,10):
┌────────────────────────────────────┐
│████████████████░░░░░░░░░░░░░░ 45%│
└────────────────────────────────────┘
Button Indicator (Bottom):
╔═══╗
║ Z ║ ← Press repeatedly!
╚═══╝
Text: "MASH Z!"
```
#### Configuration Parameters
```lua
bar_fill = 0 -- Current fill level (0-100)
max_fill = 100 -- Target fill level
fill_per_press = 8 -- Points gained per button press
base_degradation = 0.15 -- Base degradation per frame
degradation_multiplier = 0.006 -- Increases degradation with bar fill
button_press_duration = 8 -- Visual feedback duration (frames)
```
#### Usage Example
```lua
-- Start button mash minigame
MinigameButtonMashWindow.start(WINDOW_GAME)
-- Or from any window
MinigameButtonMashWindow.start(WINDOW_POPUP)
```
#### Color Coding
- **Green**: Low fill (0-33%)
- **Medium (NPC color)**: Medium fill (34-66%)
- **Yellow (Item color)**: High fill (67-100%)
---
## Rhythm Minigame
**File**: `inc/window/window.minigame.rhythm.lua`
### Description
A timing-based minigame where the player must press Z when a moving line crosses a green target zone. The target zone shrinks with each successful hit, making the game progressively more challenging.
### Gameplay Mechanics
- **Objective**: Score 10 points by timing button presses correctly

View File

@@ -7,6 +7,7 @@ Songs = {
name = "Test Song", name = "Test Song",
bpm = 120, -- Beats per minute (for reference) bpm = 120, -- Beats per minute (for reference)
fps = 60, -- Frames per second (TIC-80 default) fps = 60, -- Frames per second (TIC-80 default)
end_frame = 570, -- Frame when song ends (last note)
-- Arrow spawn pattern -- Arrow spawn pattern
-- Each entry defines when (in frames) and which direction arrow spawns -- Each entry defines when (in frames) and which direction arrow spawns
@@ -54,12 +55,64 @@ Songs = {
{frame = 570, dir = "right"} {frame = 570, dir = "right"}
} }
}, },
test_song_2 = {
name = "Test Song 2",
bpm = 120, -- Beats per minute (for reference)
fps = 60, -- Frames per second (TIC-80 default)
end_frame = 570, -- Frame when song ends (last note)
-- Arrow spawn pattern
-- Each entry defines when (in frames) and which direction arrow spawns
-- Formula: frame = (beat / bpm) * 60 * fps
-- For 120 BPM: 1 beat = 30 frames, 2 beats = 60 frames, etc.
pattern = {
-- Beat 1-4 (intro)
{frame = 30, dir = "left"},
{frame = 60, dir = "down"},
{frame = 90, dir = "up"},
{frame = 120, dir = "right"},
-- Beat 5-8 (faster)
{frame = 135, dir = "left"},
{frame = 150, dir = "right"},
{frame = 165, dir = "left"},
{frame = 180, dir = "right"},
-- Beat 9-12 (complex pattern)
{frame = 210, dir = "left"},
{frame = 210, dir = "right"}, -- simultaneous
{frame = 240, dir = "up"},
{frame = 240, dir = "down"}, -- simultaneous
{frame = 270, dir = "left"},
{frame = 300, dir = "right"},
-- Beat 13-16 (rapid sequence)
{frame = 330, dir = "left"},
{frame = 345, dir = "down"},
{frame = 360, dir = "up"},
{frame = 375, dir = "right"},
{frame = 390, dir = "left"},
{frame = 405, dir = "down"},
{frame = 420, dir = "up"},
{frame = 435, dir = "right"},
-- Beat 17-20 (finale)
{frame = 465, dir = "up"},
{frame = 465, dir = "down"},
{frame = 495, dir = "left"},
{frame = 495, dir = "right"},
{frame = 525, dir = "up"},
{frame = 540, dir = "down"},
{frame = 555, dir = "left"},
{frame = 570, dir = "right"}
}
},
-- Random mode (no predefined pattern, spawns randomly) -- Random mode (no predefined pattern, spawns randomly)
random = { random = {
name = "Random Mode", name = "Random Mode",
bpm = 0, -- Not applicable for random mode bpm = 0, -- Not applicable for random mode
fps = 60, fps = 60,
end_frame = nil, -- No end frame for random mode
pattern = {} -- Empty, will spawn randomly in game pattern = {} -- Empty, will spawn randomly in game
} }
} }

View File

@@ -309,6 +309,7 @@ local function get_initial_data()
text = "Test your reflexes! Hit the arrows in time with the music. Choose your difficulty:", text = "Test your reflexes! Hit the arrows in time with the music. Choose your difficulty:",
options = { options = {
{label = "Test Song", next_node = "test"}, {label = "Test Song", next_node = "test"},
{label = "Test Song 2", next_node = "test_2"},
{label = "Random Mode", next_node = "random"}, {label = "Random Mode", next_node = "random"},
{label = "Not now.", next_node = "dialog_end"} {label = "Not now.", next_node = "dialog_end"}
} }
@@ -319,6 +320,12 @@ local function get_initial_data()
{label = "Start!", next_node = "__MINIGAME_DDR:test_song__"} {label = "Start!", next_node = "__MINIGAME_DDR:test_song__"}
} }
}, },
test_2 = {
text = "Test song 2 selected. Show me what you got!",
options = {
{label = "Start!", next_node = "__MINIGAME_DDR:test_song_2__"}
}
},
random = { random = {
text = "Random arrows! No pattern, just react!", text = "Random arrows! No pattern, just react!",
options = { options = {

View File

@@ -150,7 +150,7 @@ end
function MinigameDDRWindow.update() function MinigameDDRWindow.update()
local mg = Context.minigame_ddr local mg = Context.minigame_ddr
-- Check for completion -- Check for completion (bar filled to 100%)
if mg.bar_fill >= mg.max_fill then if mg.bar_fill >= mg.max_fill then
Context.active_window = mg.return_window Context.active_window = mg.return_window
return return
@@ -159,6 +159,16 @@ function MinigameDDRWindow.update()
-- Increment frame counter -- Increment frame counter
mg.frame_counter = mg.frame_counter + 1 mg.frame_counter = mg.frame_counter + 1
-- Check if song has ended (pattern mode only)
if mg.use_pattern and mg.current_song and mg.current_song.end_frame then
-- Song has ended if we've passed the end frame AND all arrows are cleared
if mg.frame_counter > mg.current_song.end_frame and #mg.arrows == 0 then
-- Song complete! Return to previous window
Context.active_window = mg.return_window
return
end
end
-- Spawn arrows based on mode (pattern or random) -- Spawn arrows based on mode (pattern or random)
if mg.use_pattern and mg.current_song and mg.current_song.pattern then if mg.use_pattern and mg.current_song and mg.current_song.pattern then
-- Pattern-based spawning (synced to song) -- Pattern-based spawning (synced to song)
@@ -177,13 +187,6 @@ function MinigameDDRWindow.update()
break break
end end
end end
-- If we've finished the pattern, check if we should loop or end
if mg.pattern_index > #pattern then
-- Pattern complete - could loop or switch to random mode
-- For now, keep playing but stop spawning new arrows
-- Optionally: mg.pattern_index = 1 to loop
end
else else
-- Random spawning mode (original behavior) -- Random spawning mode (original behavior)
mg.arrow_spawn_timer = mg.arrow_spawn_timer + 1 mg.arrow_spawn_timer = mg.arrow_spawn_timer + 1

View File

@@ -13,16 +13,18 @@ function PopupWindow.set_dialog_node(node_key)
-- Special handling for DDR minigame trigger -- Special handling for DDR minigame trigger
-- Format: __MINIGAME_DDR__ or __MINIGAME_DDR:song_key__ -- Format: __MINIGAME_DDR__ or __MINIGAME_DDR:song_key__
local song_key = node_key:match("^__MINIGAME_DDR:(.+)__$")
if song_key then
-- Extract song key from the node (format: __MINIGAME_DDR/test_song__)
trace('Playing song: ' .. song_key)
MinigameDDRWindow.start(WINDOW_GAME, song_key)
return
end
if node_key == "__MINIGAME_DDR__" then if node_key == "__MINIGAME_DDR__" then
MinigameDDRWindow.start(WINDOW_GAME, nil) MinigameDDRWindow.start(WINDOW_GAME, nil)
return return
end end
if node_key:sub(1, 16) == "__MINIGAME_DDR:" then
-- Extract song key from the node (format: __MINIGAME_DDR:test_song__)
local song_key = node_key:sub(17, -3) -- Remove prefix "__MINIGAME_DDR:" and trailing "__"
MinigameDDRWindow.start(WINDOW_GAME, song_key)
return
end
local npc = Context.dialog.active_entity local npc = Context.dialog.active_entity
local node = npc.dialog[node_key] local node = npc.dialog[node_key]