feat(locale): sistema i18n YAML amb català i anglès
This commit is contained in:
@@ -64,6 +64,7 @@ namespace Config {
|
||||
KeyboardBindings keyboard_controls{}; // Defaults globals per Input
|
||||
GamepadBindings gamepad_controls{};
|
||||
bool console{false};
|
||||
std::string locale{"ca"}; // "ca" | "en" — fixat a l'arrencada, sense hot-swap
|
||||
};
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// locale.cpp - Implementació del sistema de locale
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/locale/locale.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Recorre el node YAML i aplana jerarquies en claus "a.b.c". Suporta
|
||||
// mappings (recursió) i seqüències de strings (desa "a.b.0", "a.b.1"...).
|
||||
// Altres tipus (nombres, booleans solts) s'ignoren silenciosament.
|
||||
void flatten(const fkyaml::node& node, const std::string& prefix, std::unordered_map<std::string, std::string>& out) {
|
||||
if (node.is_mapping()) {
|
||||
for (auto it = node.begin(); it != node.end(); ++it) {
|
||||
const std::string KEY = prefix.empty()
|
||||
? it.key().get_value<std::string>()
|
||||
: prefix + "." + it.key().get_value<std::string>();
|
||||
flatten(it.value(), KEY, out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (node.is_sequence()) {
|
||||
std::size_t index = 0;
|
||||
for (const auto& item : node) {
|
||||
const std::string KEY = prefix + "." + std::to_string(index);
|
||||
flatten(item, KEY, out);
|
||||
index++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (node.is_string()) {
|
||||
out[prefix] = node.get_value<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto Locale::get() -> Locale& {
|
||||
static Locale instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void Locale::load(const std::string& file_path) {
|
||||
// Normalitza traient prefix "data/" com fa StageLoader: el pack de
|
||||
// recursos indexa rutes relatives a `data/`.
|
||||
std::string normalized = file_path;
|
||||
if (normalized.starts_with("data/")) {
|
||||
normalized = normalized.substr(5);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes = Resource::Helper::loadFile(normalized);
|
||||
if (bytes.empty()) {
|
||||
std::cerr << "[Locale] no s'ha pogut load " << normalized << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string yaml_content(bytes.begin(), bytes.end());
|
||||
std::stringstream stream(yaml_content);
|
||||
fkyaml::node yaml = fkyaml::node::deserialize(stream);
|
||||
strings_.clear();
|
||||
flatten(yaml, "", strings_);
|
||||
std::cout << "[Locale] " << strings_.size() << " traduccions des de " << normalized << '\n';
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "[Locale] error parsejant " << normalized << ": " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
auto Locale::text(const std::string& key) const -> std::string {
|
||||
auto it = strings_.find(key);
|
||||
if (it != strings_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
std::cerr << "[Locale] clau no trobada: " << key << '\n';
|
||||
return key;
|
||||
}
|
||||
|
||||
auto Locale::count(const std::string& prefix) const -> std::size_t {
|
||||
std::size_t n = 0;
|
||||
while (strings_.contains(prefix + "." + std::to_string(n))) {
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
auto localeSubstitute(std::string tpl, std::string_view placeholder, std::string_view value) -> std::string {
|
||||
auto pos = tpl.find(placeholder);
|
||||
if (pos != std::string::npos) {
|
||||
tpl.replace(pos, placeholder.size(), value);
|
||||
}
|
||||
return tpl;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// locale.hpp - Sistema d'internacionalització (i18n) basat en YAML
|
||||
// © 2026 JailDesigner
|
||||
//
|
||||
// Locale amb claus en notació de punts ("notification.fullscreen_on"). El YAML
|
||||
// pot ser jerarquitzat i s'aplana en càrrega, així el consumidor només veu
|
||||
// claus planes. Suporta seqüències de strings (es desen com prefix.0,
|
||||
// prefix.1, ...). No hi ha hot-swap d'idioma: es fixa a l'arrencada des de
|
||||
// `config.yaml` (camp `locale`) i només es recarrega reiniciant el joc.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
class Locale {
|
||||
public:
|
||||
static auto get() -> Locale&;
|
||||
|
||||
Locale(const Locale&) = delete;
|
||||
Locale(Locale&&) = delete;
|
||||
auto operator=(const Locale&) -> Locale& = delete;
|
||||
auto operator=(Locale&&) -> Locale& = delete;
|
||||
|
||||
// Llig el fitxer YAML i emplena el mapping intern. Si hi ha un error de
|
||||
// parse o el fitxer no existeix, deixa el mapping com estava i ho
|
||||
// notifica per stderr.
|
||||
void load(const std::string& file_path);
|
||||
|
||||
// Retorna la traducció; si la clau no existeix, retorna la pròpia clau
|
||||
// com a fallback visible (així una clau mal escrita es detecta sense
|
||||
// trencar el render).
|
||||
[[nodiscard]] auto text(const std::string& key) const -> std::string;
|
||||
|
||||
// Compta quantes claus consecutives existeixen amb el prefix donat
|
||||
// (prefix.0, prefix.1, ...). Útil per pools indexats com stage.start.N.
|
||||
[[nodiscard]] auto count(const std::string& prefix) const -> std::size_t;
|
||||
|
||||
private:
|
||||
Locale() = default;
|
||||
~Locale() = default;
|
||||
|
||||
std::unordered_map<std::string, std::string> strings_;
|
||||
};
|
||||
|
||||
// Substitució simple d'un placeholder dins una plantilla (p.ex. "{n}" → "3").
|
||||
// S'usa per interpolar valors runtime en strings traduïdes.
|
||||
[[nodiscard]] auto localeSubstitute(std::string tpl, std::string_view placeholder, std::string_view value) -> std::string;
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/defaults/rendering.hpp"
|
||||
#include "core/defaults/window.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/coordinate_transform.hpp"
|
||||
#include "core/system/notifier.hpp"
|
||||
#include "project.h"
|
||||
@@ -250,7 +251,10 @@ void SDLManager::increaseWindowSize() {
|
||||
float new_zoom = zoom_factor_ + Defaults::Window::ZOOM_INCREMENT;
|
||||
applyZoom(new_zoom);
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(std::format("ZOOM: {:.1f}X", zoom_factor_));
|
||||
notifier->notifyInfo(localeSubstitute(
|
||||
Locale::get().text("notification.zoom"),
|
||||
"{z}",
|
||||
std::format("{:.1f}", zoom_factor_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +265,10 @@ void SDLManager::decreaseWindowSize() {
|
||||
float new_zoom = zoom_factor_ - Defaults::Window::ZOOM_INCREMENT;
|
||||
applyZoom(new_zoom);
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(std::format("ZOOM: {:.1f}X", zoom_factor_));
|
||||
notifier->notifyInfo(localeSubstitute(
|
||||
Locale::get().text("notification.zoom"),
|
||||
"{z}",
|
||||
std::format("{:.1f}", zoom_factor_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +317,7 @@ void SDLManager::toggleFullscreen() {
|
||||
Mouse::setForceHidden(is_fullscreen_);
|
||||
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(is_fullscreen_ ? "PANTALLA COMPLETA" : "MODE FINESTRA");
|
||||
notifier->notifyInfo(Locale::get().text(is_fullscreen_ ? "notification.fullscreen_on" : "notification.fullscreen_off"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +371,7 @@ void SDLManager::toggleVSync() {
|
||||
on_persist_();
|
||||
}
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(cfg_->rendering.vsync != 0 ? "VSYNC ACTIU" : "VSYNC INACTIU");
|
||||
notifier->notifyInfo(Locale::get().text(cfg_->rendering.vsync != 0 ? "notification.vsync_on" : "notification.vsync_off"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +381,7 @@ void SDLManager::toggleAntialias() {
|
||||
// No persistim: l'AA és toggleable runtime però el seu estat no es
|
||||
// guarda al YAML de moment (decisió volgudament conservadora).
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(cfg_->rendering.antialias != 0 ? "AA ACTIU" : "AA INACTIU");
|
||||
notifier->notifyInfo(Locale::get().text(cfg_->rendering.antialias != 0 ? "notification.antialias_on" : "notification.antialias_off"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +391,6 @@ void SDLManager::togglePostFx() {
|
||||
// No persistim: el toggle és per A/B testing visual, l'estat per defecte
|
||||
// del joc continua sent "postfx ON" segons defaults/YAML.
|
||||
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||
notifier->notifyInfo(NEW_STATE ? "POSTPROCESSAT ACTIU" : "POSTPROCESSAT INACTIU");
|
||||
notifier->notifyInfo(Locale::get().text(NEW_STATE ? "notification.postfx_on" : "notification.postfx_off"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/defaults/window.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
#include "core/resources/resource_loader.hpp"
|
||||
@@ -99,6 +100,10 @@ Director::Director(int argc, char* argv[])
|
||||
// Carregar o crear configuración
|
||||
ConfigYaml::loadFromFile();
|
||||
|
||||
// Carregar locale segons la config (per defecte "ca"). Si la càrrega
|
||||
// falla, Locale::text() retorna la clau crua i el joc segueix funcionant.
|
||||
Locale::get().load(std::string("locale/") + cfg_->locale + ".yaml");
|
||||
|
||||
// Inicialitzar sistema de input
|
||||
Input::init("data/gamecontrollerdb.txt");
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/input/mouse.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/rendering/sdl_manager.hpp"
|
||||
#include "core/system/notifier.hpp"
|
||||
#include "scene_context.hpp"
|
||||
@@ -71,7 +72,7 @@ namespace GlobalEvents {
|
||||
// sortida en lloc de tancar.
|
||||
auto* notifier = System::Notifier::get();
|
||||
if (notifier != nullptr && !notifier->isExitPromptActive()) {
|
||||
notifier->notifyExit("PREMEU ESC UN ALTRE COP PER EIXIR");
|
||||
notifier->notifyExit(Locale::get().text("notification.press_again_exit"));
|
||||
return true;
|
||||
}
|
||||
// Notifier inexistent (degradació elegant) o segona ESC
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace ConfigYaml {
|
||||
Config::PlayerBindings& player1 = engine_config.player1;
|
||||
Config::PlayerBindings& player2 = engine_config.player2;
|
||||
bool& console = engine_config.console;
|
||||
std::string& locale = engine_config.locale;
|
||||
} // namespace
|
||||
|
||||
// ========== FUNCIONS AUXILIARS PER CONVERSIÓ DE CONTROLES ==========
|
||||
@@ -208,6 +209,9 @@ namespace ConfigYaml {
|
||||
rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
|
||||
rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;
|
||||
|
||||
// Idioma
|
||||
locale = "ca";
|
||||
|
||||
// Version
|
||||
version = std::string(Project::VERSION);
|
||||
}
|
||||
@@ -446,6 +450,16 @@ namespace ConfigYaml {
|
||||
loadPlayer1ControlsFromYaml(yaml);
|
||||
loadPlayer2ControlsFromYaml(yaml);
|
||||
|
||||
// Idioma (opcional; valors admesos: "ca" | "en")
|
||||
if (yaml.contains("locale")) {
|
||||
try {
|
||||
auto val = yaml["locale"].get_value<std::string>();
|
||||
locale = (val == "ca" || val == "en") ? val : "ca";
|
||||
} catch (...) {
|
||||
locale = "ca";
|
||||
}
|
||||
}
|
||||
|
||||
if (console) {
|
||||
std::cout << "Config carregada correctament desde: " << config_file_path
|
||||
<< '\n';
|
||||
@@ -532,6 +546,9 @@ namespace ConfigYaml {
|
||||
file << " render_height: " << rendering.render_height
|
||||
<< " # Parell amb render_width (720, 900, 1080, 1440, 2160)\n\n";
|
||||
|
||||
file << "# IDIOMA\n";
|
||||
file << "locale: " << locale << " # ca | en\n\n";
|
||||
|
||||
// Guardar controls de jugadors
|
||||
savePlayer1ControlsToYaml(file);
|
||||
savePlayer2ControlsToYaml(file);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/system/scene_context.hpp"
|
||||
#include "game/stage_system/stage_loader.hpp"
|
||||
#include "game/systems/collision_system.hpp"
|
||||
@@ -608,7 +609,7 @@ void GameScene::drawGameOverState() {
|
||||
firework_manager_.draw();
|
||||
floating_score_manager_.draw();
|
||||
|
||||
const std::string GAME_OVER_TEXT = "GAME OVER";
|
||||
const std::string GAME_OVER_TEXT = Locale::get().text("game_screen.game_over");
|
||||
constexpr float SCALE = Defaults::Game::GameOverScreen::TEXT_SCALE;
|
||||
constexpr float SPACING = Defaults::Game::GameOverScreen::TEXT_SPACING;
|
||||
|
||||
@@ -710,7 +711,7 @@ void GameScene::drawLevelCompletedState() {
|
||||
debris_manager_.draw();
|
||||
firework_manager_.draw();
|
||||
floating_score_manager_.draw();
|
||||
drawStageMessage(StageSystem::Constants::MISSATGE_LEVEL_COMPLETED);
|
||||
drawStageMessage(Locale::get().text("stage.completed"));
|
||||
drawScoreboard();
|
||||
}
|
||||
|
||||
@@ -814,7 +815,7 @@ auto GameScene::buildScoreboard() const -> std::string {
|
||||
|
||||
// Format: "123456 03 LEVEL 01 654321 02"
|
||||
// Nota: dos espais entre seccions, mantenir ambdós slots siempre visibles
|
||||
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
|
||||
return score_p1 + " " + vides_p1 + " " + Locale::get().text("hud.level") + stage_str + " " + score_p2 + " " + vides_p2;
|
||||
}
|
||||
|
||||
// [NEW] Stage system helper methods
|
||||
@@ -946,7 +947,7 @@ void GameScene::drawContinue() {
|
||||
constexpr float SPACING = 4.0F;
|
||||
|
||||
// "CONTINUE" text (using constants)
|
||||
const std::string CONTINUE_TEXT = "CONTINUE";
|
||||
const std::string CONTINUE_TEXT = Locale::get().text("game_screen.continue");
|
||||
float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE;
|
||||
float y_ratio_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_Y_RATIO;
|
||||
|
||||
@@ -966,7 +967,10 @@ void GameScene::drawContinue() {
|
||||
|
||||
// "CONTINUES LEFT" (conditional + using constants)
|
||||
if (!Defaults::Game::INFINITE_CONTINUES) {
|
||||
const std::string CONTINUES_TEXT = "CONTINUES LEFT: " + std::to_string(Defaults::Game::MAX_CONTINUES - continues_used_);
|
||||
const std::string CONTINUES_TEXT = localeSubstitute(
|
||||
Locale::get().text("game_screen.continues_left"),
|
||||
"{n}",
|
||||
std::to_string(Defaults::Game::MAX_CONTINUES - continues_used_));
|
||||
float escala_info = Defaults::Game::ContinueScreen::INFO_TEXT_SCALE;
|
||||
float y_ratio_info = Defaults::Game::ContinueScreen::INFO_TEXT_Y_RATIO;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/graphics/shape_loader.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "core/math/easing.hpp"
|
||||
#include "core/rendering/shape_renderer.hpp"
|
||||
#include "core/system/scene_context.hpp"
|
||||
@@ -602,7 +603,7 @@ void TitleScene::draw() {
|
||||
mostrar_text = (std::sin(FASE) > 0.0F);
|
||||
}
|
||||
if (mostrar_text) {
|
||||
const std::string MAIN_TEXT = "PRESS START TO PLAY";
|
||||
const std::string MAIN_TEXT = Locale::get().text("title.press_start");
|
||||
const float MAIN_SCALE = Defaults::Title::Layout::PRESS_START_SCALE;
|
||||
const float CENTRE_X = Defaults::Game::WIDTH / 2.0F;
|
||||
const float CENTRE_Y = Defaults::Game::HEIGHT * Defaults::Title::Layout::PRESS_START_POS;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -78,24 +77,4 @@ namespace StageSystem {
|
||||
}
|
||||
};
|
||||
|
||||
// Constants per messages de transición
|
||||
namespace Constants {
|
||||
// Pool de messages per start de level (selecció aleatòria)
|
||||
inline constexpr std::array<const char*, 12> MISSATGES_LEVEL_START = {
|
||||
"ORNI ALERT!",
|
||||
"INCOMING ORNIS!",
|
||||
"ROLLING THREAT!",
|
||||
"ENEMY WAVE!",
|
||||
"WAVE OF ORNIS DETECTED!",
|
||||
"NEXT SWARM APPROACHING!",
|
||||
"BRACE FOR THE NEXT WAVE!",
|
||||
"ANOTHER ATTACK INCOMING!",
|
||||
"SENSORS DETECT HOSTILE ORNIS...",
|
||||
"UNIDENTIFIED ROLLING OBJECTS INBOUND!",
|
||||
"ENEMY FORCES MOBILIZING!",
|
||||
"PREPARE FOR IMPACT!"};
|
||||
|
||||
constexpr const char* MISSATGE_LEVEL_COMPLETED = "GOOD JOB COMMANDER!";
|
||||
} // namespace Constants
|
||||
|
||||
} // namespace StageSystem
|
||||
|
||||
@@ -10,164 +10,164 @@
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/locale/locale.hpp"
|
||||
#include "stage_config.hpp"
|
||||
|
||||
namespace StageSystem {
|
||||
|
||||
StageManager::StageManager(const StageSystemConfig* config)
|
||||
: config_(config)
|
||||
{
|
||||
if (config_ == nullptr) {
|
||||
std::cerr << "[StageManager] Error: config es null" << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::init() {
|
||||
stage_actual_ = 1;
|
||||
loadStage(stage_actual_);
|
||||
changeState(EstatStage::INIT_HUD);
|
||||
|
||||
std::cout << "[StageManager] Inicialitzat a stage " << static_cast<int>(stage_actual_)
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
void StageManager::update(float delta_time, bool pause_spawn) {
|
||||
switch (estat_) {
|
||||
case EstatStage::INIT_HUD:
|
||||
processInitHud(delta_time);
|
||||
break;
|
||||
|
||||
case EstatStage::LEVEL_START:
|
||||
processLevelStart(delta_time);
|
||||
break;
|
||||
|
||||
case EstatStage::PLAYING:
|
||||
processPlaying(delta_time, pause_spawn);
|
||||
break;
|
||||
|
||||
case EstatStage::LEVEL_COMPLETED:
|
||||
processLevelCompleted(delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::markStageCompleted() {
|
||||
std::cout << "[StageManager] Stage " << static_cast<int>(stage_actual_) << " completat!"
|
||||
<< '\n';
|
||||
changeState(EstatStage::LEVEL_COMPLETED);
|
||||
}
|
||||
|
||||
auto StageManager::isGameComplete() const -> bool {
|
||||
return stage_actual_ >= config_->metadata.total_stages &&
|
||||
estat_ == EstatStage::LEVEL_COMPLETED &&
|
||||
timer_transicio_ <= 0.0F;
|
||||
}
|
||||
|
||||
auto StageManager::getCurrentConfig() const -> const StageConfig* {
|
||||
return config_->findStage(stage_actual_);
|
||||
}
|
||||
|
||||
void StageManager::changeState(EstatStage nou_estat) {
|
||||
estat_ = nou_estat;
|
||||
|
||||
// Set timer based on state type
|
||||
if (nou_estat == EstatStage::INIT_HUD) {
|
||||
timer_transicio_ = Defaults::Game::INIT_HUD_DURATION;
|
||||
} else if (nou_estat == EstatStage::LEVEL_START) {
|
||||
timer_transicio_ = Defaults::Game::LEVEL_START_DURATION;
|
||||
} else if (nou_estat == EstatStage::LEVEL_COMPLETED) {
|
||||
timer_transicio_ = Defaults::Game::LEVEL_COMPLETED_DURATION;
|
||||
}
|
||||
|
||||
// Select random message when entering LEVEL_START
|
||||
if (nou_estat == EstatStage::LEVEL_START) {
|
||||
size_t index = static_cast<size_t>(std::rand()) % Constants::MISSATGES_LEVEL_START.size();
|
||||
missatge_level_start_actual_ = Constants::MISSATGES_LEVEL_START[index];
|
||||
|
||||
// [NOU] Iniciar música al entrar en LEVEL_START (después de INIT_HUD)
|
||||
// Solo si no está sonant ya (per evitar reset en loops posteriors)
|
||||
if (Audio::getMusicState() != Audio::MusicState::PLAYING) {
|
||||
Audio::get()->playMusic("game.ogg");
|
||||
StageManager::StageManager(const StageSystemConfig* config)
|
||||
: config_(config) {
|
||||
if (config_ == nullptr) {
|
||||
std::cerr << "[StageManager] Error: config es null" << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "[StageManager] Canvi de state: ";
|
||||
switch (nou_estat) {
|
||||
case EstatStage::INIT_HUD:
|
||||
std::cout << "INIT_HUD";
|
||||
break;
|
||||
case EstatStage::LEVEL_START:
|
||||
std::cout << "LEVEL_START";
|
||||
break;
|
||||
case EstatStage::PLAYING:
|
||||
std::cout << "PLAYING";
|
||||
break;
|
||||
case EstatStage::LEVEL_COMPLETED:
|
||||
std::cout << "LEVEL_COMPLETED";
|
||||
break;
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
void StageManager::processInitHud(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
changeState(EstatStage::LEVEL_START);
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::processLevelStart(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
changeState(EstatStage::PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::processPlaying(float delta_time, bool pause_spawn) {
|
||||
|
||||
// Update spawn controller (pauses when pause_spawn = true)
|
||||
// Note: The actual enemy array update happens in GameScene::update()
|
||||
// This is just for internal timekeeping
|
||||
(void)delta_time; // Spawn controller is updated externally
|
||||
(void)pause_spawn; // Passed to spawn_controller_.update() by GameScene
|
||||
}
|
||||
|
||||
void StageManager::processLevelCompleted(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
// Advance to next stage
|
||||
stage_actual_++;
|
||||
|
||||
// Loop back to stage 1 after final stage
|
||||
if (stage_actual_ > config_->metadata.total_stages) {
|
||||
stage_actual_ = 1;
|
||||
std::cout << "[StageManager] Todas las stages completades! Tornant a stage 1"
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
// Load next stage
|
||||
void StageManager::init() {
|
||||
stage_actual_ = 1;
|
||||
loadStage(stage_actual_);
|
||||
changeState(EstatStage::LEVEL_START);
|
||||
}
|
||||
}
|
||||
changeState(EstatStage::INIT_HUD);
|
||||
|
||||
void StageManager::loadStage(uint8_t stage_id) {
|
||||
const StageConfig* stage_config = config_->findStage(stage_id);
|
||||
if (stage_config == nullptr) {
|
||||
std::cerr << "[StageManager] Error: no es pot trobar stage " << static_cast<int>(stage_id)
|
||||
std::cout << "[StageManager] Inicialitzat a stage " << static_cast<int>(stage_actual_)
|
||||
<< '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure spawn controller
|
||||
spawn_controller_.configure(stage_config);
|
||||
spawn_controller_.start();
|
||||
void StageManager::update(float delta_time, bool pause_spawn) {
|
||||
switch (estat_) {
|
||||
case EstatStage::INIT_HUD:
|
||||
processInitHud(delta_time);
|
||||
break;
|
||||
|
||||
std::cout << "[StageManager] Carregat stage " << static_cast<int>(stage_id) << ": "
|
||||
<< static_cast<int>(stage_config->total_enemies) << " enemigos" << '\n';
|
||||
}
|
||||
case EstatStage::LEVEL_START:
|
||||
processLevelStart(delta_time);
|
||||
break;
|
||||
|
||||
case EstatStage::PLAYING:
|
||||
processPlaying(delta_time, pause_spawn);
|
||||
break;
|
||||
|
||||
case EstatStage::LEVEL_COMPLETED:
|
||||
processLevelCompleted(delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::markStageCompleted() {
|
||||
std::cout << "[StageManager] Stage " << static_cast<int>(stage_actual_) << " completat!"
|
||||
<< '\n';
|
||||
changeState(EstatStage::LEVEL_COMPLETED);
|
||||
}
|
||||
|
||||
auto StageManager::isGameComplete() const -> bool {
|
||||
return stage_actual_ >= config_->metadata.total_stages &&
|
||||
estat_ == EstatStage::LEVEL_COMPLETED &&
|
||||
timer_transicio_ <= 0.0F;
|
||||
}
|
||||
|
||||
auto StageManager::getCurrentConfig() const -> const StageConfig* {
|
||||
return config_->findStage(stage_actual_);
|
||||
}
|
||||
|
||||
void StageManager::changeState(EstatStage nou_estat) {
|
||||
estat_ = nou_estat;
|
||||
|
||||
// Set timer based on state type
|
||||
if (nou_estat == EstatStage::INIT_HUD) {
|
||||
timer_transicio_ = Defaults::Game::INIT_HUD_DURATION;
|
||||
} else if (nou_estat == EstatStage::LEVEL_START) {
|
||||
timer_transicio_ = Defaults::Game::LEVEL_START_DURATION;
|
||||
} else if (nou_estat == EstatStage::LEVEL_COMPLETED) {
|
||||
timer_transicio_ = Defaults::Game::LEVEL_COMPLETED_DURATION;
|
||||
}
|
||||
|
||||
// Select random message when entering LEVEL_START
|
||||
if (nou_estat == EstatStage::LEVEL_START) {
|
||||
const std::size_t POOL = Locale::get().count("stage.start");
|
||||
const std::size_t INDEX = (POOL == 0) ? 0 : static_cast<std::size_t>(std::rand()) % POOL;
|
||||
missatge_level_start_actual_ = Locale::get().text("stage.start." + std::to_string(INDEX));
|
||||
|
||||
// [NOU] Iniciar música al entrar en LEVEL_START (después de INIT_HUD)
|
||||
// Solo si no está sonant ya (per evitar reset en loops posteriors)
|
||||
if (Audio::getMusicState() != Audio::MusicState::PLAYING) {
|
||||
Audio::get()->playMusic("game.ogg");
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "[StageManager] Canvi de state: ";
|
||||
switch (nou_estat) {
|
||||
case EstatStage::INIT_HUD:
|
||||
std::cout << "INIT_HUD";
|
||||
break;
|
||||
case EstatStage::LEVEL_START:
|
||||
std::cout << "LEVEL_START";
|
||||
break;
|
||||
case EstatStage::PLAYING:
|
||||
std::cout << "PLAYING";
|
||||
break;
|
||||
case EstatStage::LEVEL_COMPLETED:
|
||||
std::cout << "LEVEL_COMPLETED";
|
||||
break;
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
void StageManager::processInitHud(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
changeState(EstatStage::LEVEL_START);
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::processLevelStart(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
changeState(EstatStage::PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::processPlaying(float delta_time, bool pause_spawn) {
|
||||
// Update spawn controller (pauses when pause_spawn = true)
|
||||
// Note: The actual enemy array update happens in GameScene::update()
|
||||
// This is just for internal timekeeping
|
||||
(void)delta_time; // Spawn controller is updated externally
|
||||
(void)pause_spawn; // Passed to spawn_controller_.update() by GameScene
|
||||
}
|
||||
|
||||
void StageManager::processLevelCompleted(float delta_time) {
|
||||
timer_transicio_ -= delta_time;
|
||||
|
||||
if (timer_transicio_ <= 0.0F) {
|
||||
// Advance to next stage
|
||||
stage_actual_++;
|
||||
|
||||
// Loop back to stage 1 after final stage
|
||||
if (stage_actual_ > config_->metadata.total_stages) {
|
||||
stage_actual_ = 1;
|
||||
std::cout << "[StageManager] Todas las stages completades! Tornant a stage 1"
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
// Load next stage
|
||||
loadStage(stage_actual_);
|
||||
changeState(EstatStage::LEVEL_START);
|
||||
}
|
||||
}
|
||||
|
||||
void StageManager::loadStage(uint8_t stage_id) {
|
||||
const StageConfig* stage_config = config_->findStage(stage_id);
|
||||
if (stage_config == nullptr) {
|
||||
std::cerr << "[StageManager] Error: no es pot trobar stage " << static_cast<int>(stage_id)
|
||||
<< '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure spawn controller
|
||||
spawn_controller_.configure(stage_config);
|
||||
spawn_controller_.start();
|
||||
|
||||
std::cout << "[StageManager] Carregat stage " << static_cast<int>(stage_id) << ": "
|
||||
<< static_cast<int>(stage_config->total_enemies) << " enemigos" << '\n';
|
||||
}
|
||||
|
||||
} // namespace StageSystem
|
||||
|
||||
Reference in New Issue
Block a user