feat(controls): redefinició de tecles i botons del mando des del menú de servei

This commit is contained in:
JailDesigner committed 2026-04-29 19:54:47 +02:00
1 parent 4739354875
commit 37fdcb4751
15 files changed
+1169 -80

No files matched your search

+238
View File
@@ -0,0 +1,238 @@
#include "core/input/define_buttons.hpp"
#include <algorithm>
#include <utility>
#include "core/locale/locale.hpp"
#include "core/rendering/screen.hpp"
#include "core/rendering/text.hpp"
#include "core/ui/ui_config.hpp"
#include "utils/defines.hpp"
namespace {
// Threshold per als triggers L2/R2 — equivalent a CC (16384 ≈ 50%).
constexpr Sint16 TRIGGER_THRESHOLD = 16384;
// Geometria de l'overlay (canvas lògic 320×240).
constexpr int BOX_WIDTH = 240;
constexpr int PAD_Y = 8;
constexpr int LINE_GAP = 4;
constexpr Uint8 BG_ALPHA = 230;
} // namespace
void DefineButtons::init(std::shared_ptr<Text> title_font,
std::shared_ptr<Text> items_font,
const Palette& ui_palette) {
title_font_ = std::move(title_font);
items_font_ = std::move(items_font);
ui_palette_ = ui_palette;
}
auto DefineButtons::enable(SDL_Gamepad* pad, std::string name, std::string path) -> bool {
if (pad == nullptr) { return false; }
pad_ = pad;
pad_name_ = std::move(name);
pad_path_ = std::move(path);
enabled_ = true;
finished_ = false;
closing_ = false;
message_shown_ = false;
message_timer_ms_ = 0;
l2_was_pressed_ = false;
r2_was_pressed_ = false;
resetSequence();
return true;
}
void DefineButtons::disable() {
enabled_ = false;
finished_ = false;
closing_ = false;
message_shown_ = false;
message_timer_ms_ = 0;
l2_was_pressed_ = false;
r2_was_pressed_ = false;
pad_ = nullptr;
pad_name_.clear();
pad_path_.clear();
sequence_.clear();
index_ = 0;
}
void DefineButtons::resetSequence() {
// Ordre: ATTACK, JUMP, SERVICE — replica l'estructura de
// `clearButtons()` de coffee_crisis_arcade_edition (seqüència ordenada
// d'accions amb la SERVICE al final) adaptada al joc actual. La label
// de SERVICE va a una clau pròpia (`menu.items.service`) per no
// reutilitzar la del submenú dels mandos.
sequence_.clear();
sequence_.push_back({"menu.items.attack", GamepadBindings::Action::ATTACK, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
sequence_.push_back({"menu.items.jump", GamepadBindings::Action::JUMP, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
sequence_.push_back({"menu.items.service", GamepadBindings::Action::SERVICE, static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)});
index_ = 0;
}
auto DefineButtons::isInUse(int button) const -> bool {
return std::ranges::any_of(sequence_, [button](const ButtonAssignment& a) {
return a.button == button;
});
}
auto DefineButtons::isReadyToClose() const -> bool {
// Llest per tancar quan: la seqüència ha acabat, ja s'ha mostrat el
// missatge, està en mode tancament, i el timer ha expirat. Replica el
// patró de `coffee_crisis_arcade_edition::DefineButtons::isReadyToClose`,
// simplificat perquè ací no tenim animació de WindowMessage.
return finished_ && message_shown_ && closing_ && message_timer_ms_ <= 0;
}
void DefineButtons::update(int delta_ms) {
if (!enabled_) { return; }
if (finished_ && message_shown_) {
message_timer_ms_ -= delta_ms;
if (message_timer_ms_ <= 0 && !closing_) {
closing_ = true;
message_timer_ms_ = 0; // anchor a 0 per a `isReadyToClose`.
}
}
}
void DefineButtons::handleEvent(const SDL_Event& ev) {
if (!enabled_) { return; }
switch (ev.type) {
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
handleButtonDown(ev.gbutton);
break;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
onButtonUp();
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
handleAxisMotion(ev.gaxis);
break;
default:
break;
}
}
void DefineButtons::assignAndAdvance(int button) {
if (finished_ || isInUse(button)) { return; }
sequence_.at(index_).button = button;
if (index_ + 1 < sequence_.size()) {
++index_;
} else {
finished_ = true;
}
}
void DefineButtons::processTrigger(int virtual_button, bool& was_pressed, bool now) {
if (now && !was_pressed) { assignAndAdvance(virtual_button); }
if (!now && was_pressed) { onButtonUp(); }
was_pressed = now;
}
void DefineButtons::handleButtonDown(const SDL_GamepadButtonEvent& ev) {
// Filtra: només acceptem events del mando que estem configurant.
// (Per l'instance_id no podem comparar directament; obtenim el handle
// i el comparem.)
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
if (source != pad_) { return; }
assignAndAdvance(static_cast<int>(ev.button));
}
void DefineButtons::handleAxisMotion(const SDL_GamepadAxisEvent& ev) {
const SDL_Gamepad* source = SDL_GetGamepadFromID(ev.which);
if (source != pad_) { return; }
// Triggers L2/R2 com a "botons" amb edge-detect (transició inactiu →
// actiu). Replica `doControllerAxisMotion` de
// coffee_crisis_arcade_edition.
const bool NOW = ev.value > TRIGGER_THRESHOLD;
if (ev.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER) {
processTrigger(GamepadBindings::TRIGGER_L2_AS_BUTTON, l2_was_pressed_, NOW);
} else if (ev.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
processTrigger(GamepadBindings::TRIGGER_R2_AS_BUTTON, r2_was_pressed_, NOW);
}
}
void DefineButtons::onButtonUp() {
if (!finished_ || message_shown_) { return; }
persistAndShowComplete();
}
void DefineButtons::persistAndShowComplete() {
std::unordered_map<GamepadBindings::Action, int> mapping;
for (const auto& a : sequence_) {
mapping[a.action] = a.button;
}
GamepadBindings::get().setBindings(pad_name_, pad_path_, mapping);
message_shown_ = true;
message_timer_ms_ = MESSAGE_DISPLAY_DURATION_MS;
}
void DefineButtons::render() const {
if (!enabled_ || !title_font_ || !items_font_) { return; }
const int TITLE_H = title_font_->getLineHeight();
const int ITEM_H = items_font_->getLineHeight();
const int TITLE_KERNING = title_font_->getDefaultKerning();
const int ITEM_KERNING = items_font_->getDefaultKerning();
// Calcular l'alçada total: títol + 2 línies (instrucció + label).
int box_h = (PAD_Y * 2) + TITLE_H + LINE_GAP + (ITEM_H * 2) + LINE_GAP;
int box_x = (GameCanvas::WIDTH - BOX_WIDTH) / 2;
int box_y = (GameCanvas::HEIGHT - box_h) / 2;
const auto BG_ARGB = ui_palette_[UiConfig::get().menuBg()];
const auto BORDER_ARGB = ui_palette_[UiConfig::get().menuBorder()];
Screen::get().blendRect(box_x, box_y, BOX_WIDTH, box_h, BG_ARGB, BG_ALPHA);
Screen::get().fillRect(box_x, box_y, BOX_WIDTH, 1, BORDER_ARGB);
Screen::get().fillRect(box_x, box_y + box_h - 1, BOX_WIDTH, 1, BORDER_ARGB);
Screen::get().fillRect(box_x, box_y, 1, box_h, BORDER_ARGB);
Screen::get().fillRect(box_x + BOX_WIDTH - 1, box_y, 1, box_h, BORDER_ARGB);
int cursor_y = box_y + PAD_Y;
const int CENTER_X = box_x + (BOX_WIDTH / 2);
// Títol: nom del mando.
const std::string TITLE = pad_name_;
title_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
TITLE,
TITLE_KERNING,
UiConfig::get().menuTitle());
cursor_y += TITLE_H + LINE_GAP;
if (message_shown_) {
// Pantalla final: missatge de configuració completa.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.define_buttons_complete"),
ITEM_KERNING,
UiConfig::get().menuValue());
return;
}
// Instrucció + acció pendent. Replica visualment el WindowMessage de
// coffee_crisis_arcade_edition.
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text("menu.values.press_button"),
ITEM_KERNING,
UiConfig::get().menuLabel());
cursor_y += ITEM_H + LINE_GAP;
if (index_ < sequence_.size()) {
const auto& current = sequence_[index_];
items_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
CENTER_X,
cursor_y,
Locale::get().text(current.label_key),
ITEM_KERNING,
UiConfig::get().menuCursor());
}
}