feat(controls): redefinició de tecles i botons del mando des del menú de servei
This commit is contained in:
1 parent
4739354875
commit
37fdcb4751
15 files changed
+1169
-80
No files matched your search
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user