feat(game): skip de countdown amb JUMP/ATTACK quan numero <= 7

This commit is contained in:
JailDesigner committed 2026-05-08 22:20:39 +02:00
1 parent a717411edd
commit 486a440668
7 files changed
+123 -20

No files matched your search

+15
View File
@@ -51,6 +51,7 @@ namespace GameOverFsm {
if (n.contains("countdown_sound_key")) {
cfg.countdown_sound_key = n["countdown_sound_key"].get_value<std::string>();
}
cfg.skip_below_or_equal = readChildInt(n, "skip_below_or_equal", cfg.skip_below_or_equal);
}
Log::info("GameOverFsm", "timings carregats des de {} (continue hold {}ms, fade {}ms)", yaml_path, cfg.continue_text_hold_ms, cfg.fade_out_ms);
@@ -118,6 +119,20 @@ namespace GameOverFsm {
t.elapsed_ms = 0;
}
void skipToNextCountdown(State current, Transition& t, const Config& cfg) {
if (current != State::CONTINUE_PROMPT) { return; }
if (t.phase != 1) { return; }
const int START = std::max(0, cfg.continue_count_start);
const int STEP_COUNT = START + 1;
const int STEP_MS = std::max(1, cfg.continue_text_hold_ms / STEP_COUNT);
const int CONSUMED = t.elapsed_ms / STEP_MS;
const int CURRENT = std::clamp(START - CONSUMED, 0, START);
// No saltem si ja som al 0: el següent "número" és la transició
// a GAME_OVER, que `advance` ja gestiona quan el hold s'esgota.
if (CURRENT <= 0) { return; }
t.elapsed_ms = (CONSUMED + 1) * STEP_MS;
}
auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int {
if (current != State::CONTINUE_PROMPT) { return 0; }
if (t.phase != 1) { return 0; }
+17
View File
@@ -92,6 +92,14 @@ namespace GameOverFsm {
// canvi de número, i quan apareix el 0). NO sona en la
// transició posterior cap a GAME_OVER. Buit = sense so.
std::string countdown_sound_key{"countdown.wav"};
// Llindar a partir del qual qualsevol botó de salt o atac (de
// qualsevol jugador) salta al següent número del countdown.
// Exemple: 7 → mentre el número visible és 7, 6, 5, ..., 1
// (no a 0, que ja no té "següent número") un botó d'acció
// avança al següent. Si el valor és més gran que `start` el
// skip queda desactivat efectivament. -1 = mai.
int skip_below_or_equal{7};
};
// Llegeix la configuració des del YAML. Si el fitxer manca o falta
@@ -127,6 +135,15 @@ namespace GameOverFsm {
// CONTINUE_PROMPT phase 1, retorna 0 (no s'hauria de pintar).
[[nodiscard]] auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int;
// Salta al següent número del countdown (decrementa el valor
// visible en una unitat). Només actua dins de CONTINUE_PROMPT
// phase 1 i quan el valor actual és > 0 — al 0 ja no hi ha
// "següent número", la fase ha d'esgotar-se de manera natural i
// el caller es recolza en `advance` per al salt a GAME_OVER. La
// GameScene comprova també el llindar `skip_below_or_equal`
// abans de cridar; aquesta funció, per simplicitat, no en sap.
void skipToNextCountdown(State current, Transition& t, const Config& cfg);
// Nom textual per a logs i tests.
[[nodiscard]] auto stateName(State s) -> const char*;
+37 -20
View File
@@ -1231,26 +1231,43 @@ void GameScene::updateGameOverFlow(int delta_ms) {
}
}
// Tick del so del countdown: cada vegada que el número visible
// canvia (incloent l'aparició del primer valor i del 0), reproduïm
// el so configurat. La detecció es fa post-`advance` perquè el
// canvi de fase 0 → 1 arribi a aparèixer com a transició -1 → start.
// En la transició cap a GAME_OVER el state ja no és CONTINUE_PROMPT
// i la branca s'omet — així no sona un "tick" extra al saltar.
if (game_over_state_ == GameOverFsm::State::CONTINUE_PROMPT) {
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
game_over_state_,
game_over_transition_,
game_over_config_);
if (game_over_transition_.phase == 1 && CURRENT != last_countdown_value_) {
last_countdown_value_ = CURRENT;
const auto& key = game_over_config_.countdown_sound_key;
if (!key.empty()) {
if (auto* audio = Audio::get(); audio != nullptr) {
audio->playSound(key, Audio::Group::INTERFACE);
}
}
}
// Skip i tick del so del countdown viuen en helpers per acotar
// la complexitat cognitiva d'aquesta funció. Skip primer perquè
// el tick del so soni amb el valor nou si la pulsació ha avançat.
handleCountdownSkip();
emitCountdownTickIfChanged();
}
void GameScene::handleCountdownSkip() {
if (game_over_state_ != GameOverFsm::State::CONTINUE_PROMPT) { return; }
if (game_over_transition_.phase != 1) { return; }
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
game_over_state_,
game_over_transition_,
game_over_config_);
if (CURRENT <= 0) { return; }
if (CURRENT > game_over_config_.skip_below_or_equal) { return; }
const bool ANY_ACTION = Input::Detect::pressedActionButton(0) || Input::Detect::pressedActionButton(1);
if (!ANY_ACTION) { return; }
GameOverFsm::skipToNextCountdown(
game_over_state_,
game_over_transition_,
game_over_config_);
}
void GameScene::emitCountdownTickIfChanged() {
if (game_over_state_ != GameOverFsm::State::CONTINUE_PROMPT) { return; }
if (game_over_transition_.phase != 1) { return; }
const int CURRENT = GameOverFsm::countdownSecondsRemaining(
game_over_state_,
game_over_transition_,
game_over_config_);
if (CURRENT == last_countdown_value_) { return; }
last_countdown_value_ = CURRENT;
const auto& key = game_over_config_.countdown_sound_key;
if (key.empty()) { return; }
if (auto* audio = Audio::get(); audio != nullptr) {
audio->playSound(key, Audio::Group::INTERFACE);
}
}
+12
View File
@@ -341,6 +341,18 @@ class GameScene : public Scene {
// marcador.
void renderGameOverFade();
// Comprova si dins de CONTINUE_PROMPT phase 1 el número visible
// està al rang de skip i, si un jugador pulsa salt o atac, fa
// saltar la fase al següent número via `GameOverFsm::skipToNextCountdown`.
// Extret a mètode separat per acotar la complexitat cognitiva
// d'`updateGameOverFlow`.
void handleCountdownSkip();
// Detecta canvis de número del countdown i dispara el so
// configurat un cop per cada nou valor. També extret per
// mantenir `updateGameOverFlow` per sota del llindar de tidy.
void emitCountdownTickIfChanged();
// Helpers d'animació del rètol de transició. Reutilitzats pel
// rètol de sala (`renderRoomText`) i pel de game over
// (`renderGameOverText`). Reben `phase` i `elapsed_ms` per
+27
View File
@@ -29,4 +29,31 @@ namespace Input::Detect {
});
}
auto pressedActionButton(int player_id) -> bool {
// Teclat: les bindings actives venen d'`Options::controls()` (les
// que el menú de servei pot reescriure), no de `Defaults` cru —
// així respectem el remapping de l'usuari igual que fa el Player.
const auto& kb = (player_id == 0) ? Options::controls().keyboard_p1
: Options::controls().keyboard_p2;
const SDL_Scancode KB_JUMP = kb.jump;
const SDL_Scancode KB_ATTACK = kb.attack;
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(player_id);
const SDL_JoystickID PAD_ID = (pad != nullptr) ? SDL_GetGamepadID(pad) : 0;
const auto& slot = (player_id == 0) ? Options::controls().gamepad_p1
: Options::controls().gamepad_p2;
const int JUMP_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::JUMP);
const int ATTACK_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::ATTACK);
return std::ranges::any_of(EventBuffer::events, [&](const SDL_Event& ev) {
if (ev.type == SDL_EVENT_KEY_DOWN && (ev.key.scancode == KB_JUMP || ev.key.scancode == KB_ATTACK)) {
return true;
}
if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && pad != nullptr && ev.gbutton.which == PAD_ID) {
const int BTN = static_cast<int>(ev.gbutton.button);
if (BTN == JUMP_BTN || BTN == ATTACK_BTN) { return true; }
}
return false;
});
}
} // namespace Input::Detect
+9
View File
@@ -16,4 +16,13 @@ namespace Input::Detect {
// assignat al jugador via `Options::controls()`).
[[nodiscard]] auto pressedStart(int player_id) -> bool;
// Cert si dins dels events SDL d'aquest frame el jugador
// `player_id` (0 = P1, 1 = P2) ha pulsat el botó de salt o el
// d'atac (qualsevol dels dos). Usat per la GameScene per acceptar
// qualsevol "botó d'acció" com a senyal de skip del countdown de
// CONTINUE? un cop el número visible cau a un llindar configurable.
// Mateix patró que `pressedStart`: combina teclat (Defaults::Keys)
// i gamepad (GamepadBindings).
[[nodiscard]] auto pressedActionButton(int player_id) -> bool;
} // namespace Input::Detect