merge audita-nolint: 30→9 NOLINT (useStlAlgorithm refactors + neteja obsolets)

This commit is contained in:
JailDesigner committed 2026-05-18 17:21:45 +02:00
commit f29cc7318a
12 files changed
+190 -185

No files matched your search

-14
View File
@@ -90,7 +90,6 @@ void Audio::playMusicInternal(Ja::Music* music, const int loop, const int crossf
} }
// Pausa la música (l'estat el transiciona Engine::pauseMusic) // Pausa la música (l'estat el transiciona Engine::pauseMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::pauseMusic() { void Audio::pauseMusic() {
if (music_enabled_ && getMusicState() == MusicState::PLAYING) { if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
engine_->pauseMusic(); engine_->pauseMusic();
@@ -98,7 +97,6 @@ void Audio::pauseMusic() {
} }
// Continua la música pausada (l'estat el transiciona Engine::resumeMusic) // Continua la música pausada (l'estat el transiciona Engine::resumeMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::resumeMusic() { void Audio::resumeMusic() {
if (music_enabled_ && getMusicState() == MusicState::PAUSED) { if (music_enabled_ && getMusicState() == MusicState::PAUSED) {
engine_->resumeMusic(); engine_->resumeMusic();
@@ -106,14 +104,12 @@ void Audio::resumeMusic() {
} }
// Atura la música (l'estat el transiciona Engine::stopMusic) // Atura la música (l'estat el transiciona Engine::stopMusic)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::stopMusic() { void Audio::stopMusic() {
if (music_enabled_) { if (music_enabled_) {
engine_->stopMusic(); engine_->stopMusic();
} }
} }
// NOLINTNEXTLINE(readability-make-member-function-const)
void Audio::setMusicSpeed(float ratio) { void Audio::setMusicSpeed(float ratio) {
if (music_enabled_) { if (music_enabled_) {
engine_->setMusicSpeed(ratio); engine_->setMusicSpeed(ratio);
@@ -121,7 +117,6 @@ void Audio::setMusicSpeed(float ratio) {
} }
// Reprodueix un so per nom // Reprodueix un so per nom
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::playSound(const std::string& name, Group group) { void Audio::playSound(const std::string& name, Group group) {
if (sound_enabled_) { if (sound_enabled_) {
engine_->playSound(AudioResource::getSound(name), 0, static_cast<int>(group)); engine_->playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
@@ -129,7 +124,6 @@ void Audio::playSound(const std::string& name, Group group) {
} }
// Reprodueix un so per punter directe // Reprodueix un so per punter directe
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::playSound(Ja::Sound* sound, Group group) { void Audio::playSound(Ja::Sound* sound, Group group) {
if (sound_enabled_ && sound != nullptr) { if (sound_enabled_ && sound != nullptr) {
engine_->playSound(sound, 0, static_cast<int>(group)); engine_->playSound(sound, 0, static_cast<int>(group));
@@ -141,7 +135,6 @@ void Audio::playSound(Ja::Sound* sound, Group group) {
// processa tot el sample amb el ratio des del primer pull del callback. // processa tot el sample amb el ratio des del primer pull del callback.
// Si l'engine torna -1 (sense canal lliure) o el so no existeix, no fem // Si l'engine torna -1 (sense canal lliure) o el so no existeix, no fem
// la crida al ratio — sense efectes col·laterals. // la crida al ratio — sense efectes col·laterals.
// NOLINTNEXTLINE(readability-make-member-function-const)
void Audio::playSound(const std::string& name, Group group, float speed) { void Audio::playSound(const std::string& name, Group group, float speed) {
if (!sound_enabled_) { return; } if (!sound_enabled_) { return; }
auto* sound = AudioResource::getSound(name); auto* sound = AudioResource::getSound(name);
@@ -155,7 +148,6 @@ void Audio::playSound(const std::string& name, Group group, float speed) {
// Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no // Reprodueix un so processat per un eco definit a sounds.yaml. Si el preset no
// existeix o l'engine retorna -1 (cap de canals d'efecte plé), cau a playSound // existeix o l'engine retorna -1 (cap de canals d'efecte plé), cau a playSound
// sec — l'usuari sent el so encara que la cua no s'apliqui. // sec — l'usuari sent el so encara que la cua no s'apliqui.
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
void Audio::playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group) { void Audio::playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group) {
if (!sound_enabled_) { return; } if (!sound_enabled_) { return; }
auto* sound = AudioResource::getSound(name); auto* sound = AudioResource::getSound(name);
@@ -175,7 +167,6 @@ void Audio::playSoundWithEcho(const std::string& name, const std::string& preset
// Reprodueix un so processat per un reverb definit a sounds.yaml. Mateix // Reprodueix un so processat per un reverb definit a sounds.yaml. Mateix
// fallback que playSoundWithEcho. // fallback que playSoundWithEcho.
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
void Audio::playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group) { void Audio::playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group) {
if (!sound_enabled_) { return; } if (!sound_enabled_) { return; }
auto* sound = AudioResource::getSound(name); auto* sound = AudioResource::getSound(name);
@@ -194,7 +185,6 @@ void Audio::playSoundWithReverb(const std::string& name, const std::string& pres
} }
// Atura tots els sons // Atura tots els sons
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::stopAllSounds() { void Audio::stopAllSounds() {
if (sound_enabled_) { if (sound_enabled_) {
engine_->stopChannel(-1); engine_->stopChannel(-1);
@@ -202,7 +192,6 @@ void Audio::stopAllSounds() {
} }
// Fa una fosa de sortida de la música // Fa una fosa de sortida de la música
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::fadeOutMusic(int milliseconds) { void Audio::fadeOutMusic(int milliseconds) {
if (music_enabled_ && getMusicState() == MusicState::PLAYING) { if (music_enabled_ && getMusicState() == MusicState::PLAYING) {
engine_->fadeOutMusic(milliseconds); engine_->fadeOutMusic(milliseconds);
@@ -212,7 +201,6 @@ void Audio::fadeOutMusic(int milliseconds) {
// Registra un callback que el motor dispararà quan la pista actual acabi de // Registra un callback que el motor dispararà quan la pista actual acabi de
// drenar (times == 0 + stream buit). S'executa al mateix thread que // drenar (times == 0 + stream buit). S'executa al mateix thread que
// Audio::update (render loop); els consumidors no poden fer I/O blocant. // Audio::update (render loop); els consumidors no poden fer I/O blocant.
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_
void Audio::setOnMusicEnded(std::function<void()> callback) { void Audio::setOnMusicEnded(std::function<void()> callback) {
if (engine_) { engine_->setOnMusicEnded(std::move(callback)); } if (engine_) { engine_->setOnMusicEnded(std::move(callback)); }
} }
@@ -251,13 +239,11 @@ auto Audio::effectiveVolume(float volume, bool channel_enabled) const -> float {
} }
// Estableix el volum dels sons (float 0.0..1.0) // Estableix el volum dels sons (float 0.0..1.0)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::setSoundVolume(float sound_volume, Group group) { void Audio::setSoundVolume(float sound_volume, Group group) {
engine_->setSoundVolume(effectiveVolume(sound_volume, sound_enabled_), static_cast<int>(group)); engine_->setSoundVolume(effectiveVolume(sound_volume, sound_enabled_), static_cast<int>(group));
} }
// Estableix el volum de la música (float 0.0..1.0) // Estableix el volum de la música (float 0.0..1.0)
// NOLINTNEXTLINE(readability-make-member-function-const) — muta estat via engine_; unique_ptr::operator-> no propaga const
void Audio::setMusicVolume(float music_volume) { void Audio::setMusicVolume(float music_volume) {
engine_->setMusicVolume(effectiveVolume(music_volume, music_enabled_)); engine_->setMusicVolume(effectiveVolume(music_volume, music_enabled_));
} }
+1 -1
View File
@@ -15,7 +15,7 @@
// (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi). // (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi).
#define STB_VORBIS_HEADER_ONLY #define STB_VORBIS_HEADER_ONLY
// clang-format off // clang-format off
// NOLINTNEXTLINE(bugprone-suspicious-include) // NOLINTNEXTLINE(bugprone-suspicious-include) -- stb_vorbis és single-file: la macro de dalt limita aquest TU a només-declaracions; la implementació viu a external/stb_vorbis_impl.cpp.
#include "external/stb_vorbis.c" #include "external/stb_vorbis.c"
// clang-format on // clang-format on
+1 -1
View File
@@ -17,7 +17,7 @@ namespace Screenshot {
// Estat de mòdul: ruta base on s'escriu la subcarpeta // Estat de mòdul: ruta base on s'escriu la subcarpeta
// `screenshots/`. Buida per defecte ⇒ relativa al CWD (fallback // `screenshots/`. Buida per defecte ⇒ relativa al CWD (fallback
// si el Director no ha pogut establir la ruta per plataforma). // si el Director no ha pogut establir la ruta per plataforma).
std::string g_base_dir; // NOLINT(*-avoid-non-const-global-variables) std::string g_base_dir; // NOLINT(*-avoid-non-const-global-variables) -- estat de mòdul (singleton-like): s'estableix una vegada al startup via setBaseDir() i es llegeix N vegades; encapsulat dins del namespace anònim, no és accessible des de fora.
// Construeix la ruta del directori on van les captures: // Construeix la ruta del directori on van les captures:
// `<base>/screenshots/`. Si la base és buida, retorna // `<base>/screenshots/`. Si la base és buida, retorna
+3 -7
View File
@@ -4,6 +4,7 @@
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <numeric>
#include "utils/log.hpp" #include "utils/log.hpp"
@@ -19,12 +20,7 @@ namespace Resource {
auto Pack::calculateChecksum(const std::vector<std::uint8_t>& data) -> std::uint32_t { auto Pack::calculateChecksum(const std::vector<std::uint8_t>& data) -> std::uint32_t {
// djb2: seed 0x12345678. Compatible amb aee/projecte_2026 per si mai // djb2: seed 0x12345678. Compatible amb aee/projecte_2026 per si mai
// reutilitzem eines de diagnòstic entre projectes. // reutilitzem eines de diagnòstic entre projectes.
std::uint32_t checksum = 0x12345678; return std::accumulate(data.begin(), data.end(), std::uint32_t{0x12345678}, [](std::uint32_t acc, unsigned char b) { return ((acc << 5) + acc) + b; });
for (unsigned char b : data) {
// cppcheck-suppress useStlAlgorithm
checksum = ((checksum << 5) + checksum) + b;
}
return checksum;
} }
void Pack::xorInPlace(std::vector<std::uint8_t>& data, const std::string& key) { void Pack::xorInPlace(std::vector<std::uint8_t>& data, const std::string& key) {
@@ -151,7 +147,7 @@ namespace Resource {
return false; return false;
} }
// cppcheck-suppress useStlAlgorithm // cppcheck-suppress useStlAlgorithm -- bucle amb early-return en error (addFile fallit) + filtre amb continue: no es pot expressar net amb std::ranges::all_of perquè la lògica combina filtratge i acumulació amb sortida prematura.
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) { for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
if (!entry.is_regular_file()) { continue; } if (!entry.is_regular_file()) { continue; }
+1 -3
View File
@@ -345,9 +345,7 @@ namespace {
} // namespace } // namespace
// cppcheck-suppress constParameterReference // cppcheck-suppress constParameterReference -- Tilemap& mutable per simetria amb `toggle()` i per futures invalidacions de caches d'edició; cppcheck no pot anticipar que les futures mutacions estan previstes en el contracte.
// Tilemap& mutable per simetria amb `toggle()` i per futures
// invalidacions de caches d'edició, encara que `save()` sigui const.
void Editor::exit(Tilemap& tilemap, const EntityManager& entities) { void Editor::exit(Tilemap& tilemap, const EntityManager& entities) {
if (!active_) { return; } if (!active_) { return; }
// Constraint del nivell: cal exactament una porta i com a mínim un // Constraint del nivell: cal exactament una porta i com a mínim un
+5 -10
View File
@@ -1,6 +1,8 @@
#include "game/entities/animation_component.hpp" #include "game/entities/animation_component.hpp"
#include <algorithm>
#include <cmath> #include <cmath>
#include <iterator>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
@@ -33,8 +35,7 @@ namespace {
if (speeds.empty()) { if (speeds.empty()) {
return std::vector<float>(frame_count, K_DEFAULT_FRAME_SECONDS); return std::vector<float>(frame_count, K_DEFAULT_FRAME_SECONDS);
} }
// cppcheck-suppress useStlAlgorithm std::ranges::transform(speeds, speeds.begin(), sanitizeSpeed);
for (auto& s : speeds) { s = sanitizeSpeed(s); }
if (speeds.size() == frame_count) { return speeds; } if (speeds.size() == frame_count) { return speeds; }
if (speeds.size() > frame_count) { if (speeds.size() > frame_count) {
speeds.resize(frame_count); speeds.resize(frame_count);
@@ -50,10 +51,7 @@ namespace {
// component vàlid. // component vàlid.
auto parseFrames(const fkyaml::node& anim) -> std::vector<int> { auto parseFrames(const fkyaml::node& anim) -> std::vector<int> {
std::vector<int> frames; std::vector<int> frames;
for (const auto& f : anim["frames"]) { std::ranges::transform(anim["frames"], std::back_inserter(frames), [](const fkyaml::node& f) { return f.template get_value<int>(); });
// cppcheck-suppress useStlAlgorithm
frames.push_back(f.get_value<int>());
}
if (frames.empty()) { frames.push_back(0); } if (frames.empty()) { frames.push_back(0); }
return frames; return frames;
} }
@@ -66,10 +64,7 @@ namespace {
std::vector<float> speeds_secs; std::vector<float> speeds_secs;
const auto& fm = anim["frame_ms"]; const auto& fm = anim["frame_ms"];
if (fm.is_sequence()) { if (fm.is_sequence()) {
for (const auto& v : fm) { std::ranges::transform(fm, std::back_inserter(speeds_secs), [](const fkyaml::node& v) { return static_cast<float>(v.template get_value<int>()) / 1000.0F; });
// cppcheck-suppress useStlAlgorithm
speeds_secs.push_back(static_cast<float>(v.get_value<int>()) / 1000.0F);
}
} else { } else {
const float SECS = static_cast<float>(fm.get_value<int>()) / 1000.0F; const float SECS = static_cast<float>(fm.get_value<int>()) / 1000.0F;
speeds_secs.assign(frame_count, SECS); speeds_secs.assign(frame_count, SECS);
@@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <iterator>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -41,10 +42,7 @@ namespace {
if (!yaml.contains("animations") || !yaml["animations"].contains("swing")) { return; } if (!yaml.contains("animations") || !yaml["animations"].contains("swing")) { return; }
const auto& sw = yaml["animations"]["swing"]; const auto& sw = yaml["animations"]["swing"];
if (!sw.contains("active_frames")) { return; } if (!sw.contains("active_frames")) { return; }
for (const auto& f : sw["active_frames"]) { std::ranges::transform(sw["active_frames"], std::back_inserter(active_out), [](const fkyaml::node& f) { return f.template get_value<int>(); });
// cppcheck-suppress useStlAlgorithm
active_out.push_back(f.get_value<int>());
}
} }
} // namespace } // namespace
+13 -12
View File
@@ -53,18 +53,19 @@ class EntityFactory {
// IMPORTANT: perquè el registrador es cridi, el `.cpp` que el conté ha // IMPORTANT: perquè el registrador es cridi, el `.cpp` que el conté ha
// d'estar enllaçat directament a l'executable (no dins una STATIC lib // d'estar enllaçat directament a l'executable (no dins una STATIC lib
// sense `--whole-archive`). Tots els `concrete/*.cpp` entren a APP_SOURCES. // sense `--whole-archive`). Tots els `concrete/*.cpp` entren a APP_SOURCES.
#define REGISTER_ENTITY_AS(NAME, CLASS, TAG) \ #define REGISTER_ENTITY_AS(NAME, CLASS, TAG) \
namespace { \ namespace { \
struct TAG##_Registrar { \ struct TAG##_Registrar { \
TAG##_Registrar() { \ TAG##_Registrar() { \
EntityFactory::instance().registerCreator( \ EntityFactory::instance().registerCreator( \
NAME, \ NAME, \
[](const fkyaml::node& yaml) -> std::unique_ptr<Entity> { \ [](const fkyaml::node& yaml) -> std::unique_ptr<Entity> { \
return std::make_unique<CLASS>(yaml); \ return std::make_unique<CLASS>(yaml); \
}); \ }); \
} \ } \
}; \ }; \
const TAG##_Registrar g_##TAG##_registrar; /* NOLINT(cert-err58-cpp) */ \ /* NOLINTNEXTLINE(cert-err58-cpp) -- inicialització estàtica intencionada: el constructor registra el creator a l'EntityFactory abans de main(); el patró és l'únic mecanisme C++ que permet auto-registre per tipus sense modificar la factory. */ \
const TAG##_Registrar g_##TAG##_registrar; \
} }
#define REGISTER_ENTITY(NAME, CLASS) REGISTER_ENTITY_AS(NAME, CLASS, CLASS) #define REGISTER_ENTITY(NAME, CLASS) REGISTER_ENTITY_AS(NAME, CLASS, CLASS)
+3 -2
View File
@@ -1,5 +1,7 @@
#include "game/entities/entity_loader.hpp" #include "game/entities/entity_loader.hpp"
#include <algorithm>
#include <iterator>
#include <stdexcept> #include <stdexcept>
#include <unordered_map> #include <unordered_map>
@@ -51,8 +53,7 @@ namespace EntityLoader {
const auto& list = root["entities"]; const auto& list = root["entities"];
if (!list.is_sequence()) { return spawns; } if (!list.is_sequence()) { return spawns; }
spawns.reserve(list.size()); spawns.reserve(list.size());
// cppcheck-suppress useStlAlgorithm std::ranges::copy(list, std::back_inserter(spawns));
for (const auto& item : list) { spawns.push_back(item); }
return spawns; return spawns;
} }
+2 -2
View File
@@ -87,7 +87,7 @@ auto EntityManager::add(std::unique_ptr<Entity> entity) -> Entity* {
entity->setEntityManager(this); entity->setEntityManager(this);
Entity* raw = entity.get(); Entity* raw = entity.get();
entities_.push_back(std::move(entity)); entities_.push_back(std::move(entity));
// cppcheck-suppress returnDanglingLifetime // cppcheck-suppress returnDanglingLifetime -- fals positiu: std::move transfereix ownership del unique_ptr al vector, però l'objecte apuntat per `raw` no es mou ni es destrueix. El punter continua vàlid mentre el vector el posseesca.
return raw; return raw;
} }
@@ -124,7 +124,7 @@ auto EntityManager::addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcoph
Sarcophagus* raw = sarc.get(); Sarcophagus* raw = sarc.get();
entities_.push_back(std::move(sarc)); entities_.push_back(std::move(sarc));
++total_sarcs_; ++total_sarcs_;
// cppcheck-suppress returnDanglingLifetime // cppcheck-suppress returnDanglingLifetime -- fals positiu: std::move transfereix ownership al vector però l'objecte no es mou. `raw` continua vàlid (vegeu `add` més amunt).
return raw; return raw;
} }
+150 -129
View File
@@ -513,143 +513,115 @@ void Player::integrateAxis(float& phys, float input, float delta_time, float mot
} }
} }
// Pipeline d'estats i input separats; extracció a helpers empitjoraria la // DYING: animació `death_spinning` + pausa de respawn. Ignorem tot
// llegibilitat. La complexitat puja 1 per la guarda EXITED. // l'input (ni moviment, ni salt, ni atac). El moviment queda
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // congelat al lloc de la mort; `applyMovement` avança l'animació i
void Player::tick(float delta_time) { // detecta la fi per disparar el respawn. Mantenim els flancs de
// EXITED/DISABLED: jugador fora del joc, no fa res. Cap input, cap // botó actualitzats perquè una tecla sostinguda no es comporte com
// física, cap bus event. Continua sent al vector de jugadors per // a flanc nou en sortir del cicle.
// simplicitat (no s'esborra) però queda completament inert fins void Player::tickDying() {
// que GameScene el reactiva (respawn / spawnFromAir). (void)consumeJumpEdge();
if (isOutOfPlay()) { return; } (void)consumeAttackEdge();
input_vx_ = 0.0F;
input_vy_ = 0.0F;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
setVelX(0.0F);
setVelY(0.0F);
}
// Decrement del timer d'immunitat. Independent de l'estat: si el // En LANDING_STUN, descomptem el timer i ignorem qualsevol input
// jugador mor durant la immunitat (només via forat, que no es gateja), // (moviment i salt). Quan s'esgota, tornem a GROUNDED al pròxim
// el `respawn` rearma una nova finestra. Si la finestra acaba mentre // frame. La velocitat queda a zero — després del stun hi tornarà
// és AIRBORNE/HURT, no passa res — el gate dels hazards passa a // a haver intenció si el jugador prem alguna tecla.
// quedar obert al primer frame post-zero. void Player::tickLandingStun(float delta_time) {
if (immunity_remaining_ms_ > 0.0F) { landing_stun_remaining_ms_ -= delta_time * 1000.0F;
immunity_remaining_ms_ -= delta_time * 1000.0F; if (landing_stun_remaining_ms_ <= 0.0F) {
immunity_remaining_ms_ = std::max(immunity_remaining_ms_, 0.0F); landing_stun_remaining_ms_ = 0.0F;
state_ = State::GROUNDED;
} }
input_vx_ = 0.0F;
input_vy_ = 0.0F;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
setVelX(0.0F);
setVelY(0.0F);
// Mantenim els flancs del botó de salt actualitzats fins i tot
// durant el stun: així si l'usuari l'ha estat prement abans, no
// se'l consumeix com a salt nou en sortir del stun.
(void)consumeJumpEdge();
(void)consumeAttackEdge();
}
// DYING: animació `death_spinning` + pausa de respawn. Ignorem tot // En AIRBORNE el jugador no llig input direccional: la velocitat
// l'input (ni moviment, ni salt, ni atac). El moviment queda // horitzontal queda fixada al moment del despegue. Tampoc canvia
// congelat al lloc de la mort; `applyMovement` avança l'animació i // facing. Sí actualitzem els flancs del botó perquè la propera
// detecta la fi per disparar el respawn. Mantenim els flancs de // pulsació quan torne a terra es detecte com a flanc nou.
// botó actualitzats perquè una tecla sostinguda no es comporte com void Player::tickAirborne() {
// a flanc nou en sortir del cicle. (void)consumeJumpEdge();
if (state_ == State::DYING) { (void)consumeAttackEdge();
(void)consumeJumpEdge(); setVelX(intended_vx_);
(void)consumeAttackEdge(); setVelY(intended_vy_);
input_vx_ = 0.0F; }
input_vy_ = 0.0F;
intended_vx_ = 0.0F; // En ATTACKING el jugador queda bloquejat tot el `swing_ms +
intended_vy_ = 0.0F; // cooldown_ms`: ni input direccional, ni salt, ni atac, ni canvi
motion_time_ms_ = 0.0F; // de facing. El timer descompta amb `delta_time * 1000` per
setVelX(0.0F); // mantenir la convenció de delta_ms de la resta d'estats temporals.
setVelY(0.0F); // Mantenim els flancs dels botons actualitzats perquè una
return; // tecla mantinguda no es comporte com a flanc nou en sortir.
void Player::tickAttacking(float delta_time) {
(void)consumeJumpEdge();
(void)consumeAttackEdge();
attacking_remaining_ms_ -= delta_time * 1000.0F;
if (attacking_remaining_ms_ <= 0.0F) {
attacking_remaining_ms_ = 0.0F;
state_ = State::GROUNDED;
} }
input_vx_ = 0.0F;
input_vy_ = 0.0F;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
setVelX(0.0F);
setVelY(0.0F);
}
// Reset per frame: el resolver del GameScene el tornarà a posar a // En HURT el jugador està projectat sense control: descomptem
// true si aquest frame hi ha push actiu. Així quan el push acaba // velocitat per inèrcia (decaiment lineal fins a 0). Quan arriba a
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna // zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
// a governar-se per la velocitat real sense estat romanent. // El moviment real l'aplica `applyMovement` (igual que els altres
being_pushed_ = false; // estats), que també gestiona el xoc contra murs.
void Player::tickHurt(float delta_time) {
// En LANDING_STUN, descomptem el timer i ignorem qualsevol input (void)consumeJumpEdge();
// (moviment i salt). Quan s'esgota, tornem a GROUNDED al pròxim (void)consumeAttackEdge();
// frame. La velocitat queda a zero — després del stun hi tornarà const float DECEL = (cfg_.hurt.brake_time_ms > 0)
// a haver intenció si el jugador prem alguna tecla. ? cfg_.hurt.initial_speed_px_per_s / (static_cast<float>(cfg_.hurt.brake_time_ms) / 1000.0F)
if (state_ == State::LANDING_STUN) { : 0.0F;
landing_stun_remaining_ms_ -= delta_time * 1000.0F; const float STEP = DECEL * delta_time;
if (landing_stun_remaining_ms_ <= 0.0F) { auto decay = [STEP](float& v) {
landing_stun_remaining_ms_ = 0.0F; if (std::fabs(v) <= STEP) {
state_ = State::GROUNDED; v = 0.0F;
} else {
v -= std::copysign(STEP, v);
} }
input_vx_ = 0.0F; };
input_vy_ = 0.0F; decay(intended_vx_);
intended_vx_ = 0.0F; decay(intended_vy_);
intended_vy_ = 0.0F; if (intended_vx_ == 0.0F && intended_vy_ == 0.0F) {
motion_time_ms_ = 0.0F; state_ = State::GROUNDED;
setVelX(0.0F);
setVelY(0.0F);
// Mantenim els flancs del botó de salt actualitzats fins i tot
// durant el stun: així si l'usuari l'ha estat prement abans, no
// se'l consumeix com a salt nou en sortir del stun.
(void)consumeJumpEdge();
(void)consumeAttackEdge();
return;
}
// En AIRBORNE el jugador no llig input direccional: la velocitat
// horitzontal queda fixada al moment del despegue. Tampoc canvia
// facing. Sí actualitzem els flancs del botó perquè la propera
// pulsació quan torne a terra es detecte com a flanc nou.
if (state_ == State::AIRBORNE) {
(void)consumeJumpEdge();
(void)consumeAttackEdge();
setVelX(intended_vx_);
setVelY(intended_vy_);
return;
}
// En ATTACKING el jugador queda bloquejat tot el `swing_ms +
// cooldown_ms`: ni input direccional, ni salt, ni atac, ni canvi
// de facing. El timer descompta amb `delta_time * 1000` per
// mantenir la convenció de delta_ms de la resta d'estats temporals.
// Mantenim els flancs dels botons actualitzats perquè una
// tecla mantinguda no es comporte com a flanc nou en sortir.
if (state_ == State::ATTACKING) {
(void)consumeJumpEdge();
(void)consumeAttackEdge();
attacking_remaining_ms_ -= delta_time * 1000.0F;
if (attacking_remaining_ms_ <= 0.0F) {
attacking_remaining_ms_ = 0.0F;
state_ = State::GROUNDED;
}
input_vx_ = 0.0F;
input_vy_ = 0.0F;
intended_vx_ = 0.0F;
intended_vy_ = 0.0F;
motion_time_ms_ = 0.0F;
setVelX(0.0F);
setVelY(0.0F);
return;
}
// En HURT el jugador està projectat sense control: descomptem
// velocitat per inèrcia (decaiment lineal fins a 0). Quan arriba a
// zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
// El moviment real l'aplica `applyMovement` (igual que els altres
// estats), que també gestiona el xoc contra murs.
if (state_ == State::HURT) {
(void)consumeJumpEdge();
(void)consumeAttackEdge();
const float DECEL = (cfg_.hurt.brake_time_ms > 0)
? cfg_.hurt.initial_speed_px_per_s / (static_cast<float>(cfg_.hurt.brake_time_ms) / 1000.0F)
: 0.0F;
const float STEP = DECEL * delta_time;
auto decay = [STEP](float& v) {
if (std::fabs(v) <= STEP) {
v = 0.0F;
} else {
v -= std::copysign(STEP, v);
}
};
decay(intended_vx_);
decay(intended_vy_);
if (intended_vx_ == 0.0F && intended_vy_ == 0.0F) {
state_ = State::GROUNDED;
}
input_vx_ = 0.0F;
input_vy_ = 0.0F;
setVelX(intended_vx_);
setVelY(intended_vy_);
return;
} }
input_vx_ = 0.0F;
input_vy_ = 0.0F;
setVelX(intended_vx_);
setVelY(intended_vy_);
}
// Estat normal de joc (GROUNDED): llegeix input, integra física, actualitza
// facing i processa els flancs d'atac i salt.
void Player::tickGroundedInput(float delta_time) {
// Refresca les vinculacions des d'Options en calent: així un remap fet // Refresca les vinculacions des d'Options en calent: així un remap fet
// al menú de servei té efecte immediatament, sense esperar al pròxim // al menú de servei té efecte immediatament, sense esperar al pròxim
// GameScene::onEnter. Replica el comportament d'AEE // GameScene::onEnter. Replica el comportament d'AEE
@@ -725,6 +697,55 @@ void Player::tick(float delta_time) {
} }
} }
// Dispatcher per estat. La complexitat real viu als handlers individuals.
void Player::tick(float delta_time) {
// EXITED/DISABLED: jugador fora del joc, no fa res. Cap input, cap
// física, cap bus event. Continua sent al vector de jugadors per
// simplicitat (no s'esborra) però queda completament inert fins
// que GameScene el reactiva (respawn / spawnFromAir).
if (isOutOfPlay()) { return; }
// Decrement del timer d'immunitat. Independent de l'estat: si el
// jugador mor durant la immunitat (només via forat, que no es gateja),
// el `respawn` rearma una nova finestra. Si la finestra acaba mentre
// és AIRBORNE/HURT, no passa res — el gate dels hazards passa a
// quedar obert al primer frame post-zero.
if (immunity_remaining_ms_ > 0.0F) {
immunity_remaining_ms_ -= delta_time * 1000.0F;
immunity_remaining_ms_ = std::max(immunity_remaining_ms_, 0.0F);
}
if (state_ == State::DYING) {
tickDying();
return;
}
// Reset per frame: el resolver del GameScene el tornarà a posar a
// true si aquest frame hi ha push actiu. Així quan el push acaba
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
// a governar-se per la velocitat real sense estat romanent.
being_pushed_ = false;
if (state_ == State::LANDING_STUN) {
tickLandingStun(delta_time);
return;
}
if (state_ == State::AIRBORNE) {
tickAirborne();
return;
}
if (state_ == State::ATTACKING) {
tickAttacking(delta_time);
return;
}
if (state_ == State::HURT) {
tickHurt(delta_time);
return;
}
tickGroundedInput(delta_time);
}
void Player::refreshControlsFromOptions() { void Player::refreshControlsFromOptions() {
if (id_ != 0 && id_ != 1) { return; } if (id_ != 0 && id_ != 1) { return; }
const auto& kb = (id_ == 0) ? Options::controls().keyboard_p1 const auto& kb = (id_ == 0) ? Options::controls().keyboard_p1
+9
View File
@@ -604,6 +604,15 @@ class Player : public AnimatedSprite {
void renderShadow() const; void renderShadow() const;
private: private:
// Sub-handlers de tick(), un per estat amb sortida prematura. Mantenir-los
// a part redueix la complexitat cognitiva del dispatcher principal.
void tickDying();
void tickLandingStun(float delta_time);
void tickAirborne();
void tickAttacking(float delta_time);
void tickHurt(float delta_time);
void tickGroundedInput(float delta_time);
// Alias privat històric. Mantingut perquè viu dins de l'implementació // Alias privat històric. Mantingut perquè viu dins de l'implementació
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`. // (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
[[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); } [[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); }