diff --git a/src/app/main.cpp b/src/app/main.cpp index 4b322a0..37d11a9 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -9,7 +9,6 @@ #include "LogManager.h" #include "MainWindow.h" #include "Simulation.h" -#include "VisualsLoader.h" int main(int argc, char *argv[]) { @@ -33,10 +32,9 @@ int main(int argc, char *argv[]) } GameConfig config = ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR); - VisualsConfig visuals = VisualsLoader::load(std::string(DOTA_FACTORY_CONFIG_DIR) + "/visuals.toml"); - std::unique_ptr sim = std::make_unique(config); + std::unique_ptr sim = std::make_unique(std::move(config)); - MainWindow window(sim.get(), &config, &visuals); + MainWindow window(sim.get(), std::string(DOTA_FACTORY_CONFIG_DIR)); window.show(); const int ret = application.exec(); diff --git a/src/lib/config/GameConfig.h b/src/lib/config/GameConfig.h index 5a22c30..a37e1cf 100644 --- a/src/lib/config/GameConfig.h +++ b/src/lib/config/GameConfig.h @@ -6,8 +6,8 @@ #include "ShipsConfig.h" #include "StationsConfig.h" -// Aggregate of all five simulation config files, loaded once at startup and -// immutable for the rest of the game. See architecture.md "Config Loading". +// Aggregate of all five simulation config files. Loaded at startup and reloaded +// from disk on each game restart (REQ-CFG-RELOAD). See architecture.md "Config Loading". struct GameConfig { WorldConfig world; diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index 2486656..f561ca2 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -9,23 +9,23 @@ #include "SurfaceMask.h" #include "WaveSystem.h" -Simulation::Simulation(const GameConfig& config, unsigned int seed) - : m_config(config) +Simulation::Simulation(GameConfig config, unsigned int seed) + : m_config(std::move(config)) , m_rng(seed) , m_currentTick(0) , m_nextId(1) - , m_buildingBlocksStock(config.world.startingBuildingBlocks) + , m_buildingBlocksStock(m_config.world.startingBuildingBlocks) , m_gameOver(false) , m_hqId(kInvalidEntityId) , m_playerStation1Id(kInvalidEntityId) , m_playerStation2Id(kInvalidEntityId) - , m_beltSystem(config.world.beltSpeedTilesPerSecond) + , m_beltSystem(m_config.world.beltSpeedTilesPerSecond) { m_currentEnemyStationIds[0] = kInvalidEntityId; m_currentEnemyStationIds[1] = kInvalidEntityId; m_buildingSystem = std::make_unique( - config, + m_config, m_beltSystem, [this]() { return allocateId(); }, [this](int amount) { m_buildingBlocksStock += amount; }, @@ -39,13 +39,13 @@ Simulation::Simulation(const GameConfig& config, unsigned int seed) m_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false); }, m_rng); - m_shipSystem = std::make_unique(config, [this]() { return allocateId(); }); + m_shipSystem = std::make_unique(m_config, [this]() { return allocateId(); }); m_scrapSystem = std::make_unique([this]() { return allocateId(); }); - m_waveSystem = std::make_unique(config, m_rng); - m_combatSystem = std::make_unique(config); + m_waveSystem = std::make_unique(m_config, m_rng); + m_combatSystem = std::make_unique(m_config); // Initialize blueprint unlock state. - for (const ShipDef& def : config.ships.ships) + for (const ShipDef& def : m_config.ships.ships) { BlueprintState state; state.unlocked = def.availableFromStart; @@ -58,6 +58,17 @@ Simulation::Simulation(const GameConfig& config, unsigned int seed) Simulation::~Simulation() = default; +const GameConfig& Simulation::config() const +{ + return m_config; +} + +void Simulation::reset(GameConfig newConfig, unsigned int seed) +{ + m_config = std::move(newConfig); + reset(seed); +} + void Simulation::reset(unsigned int seed) { m_rng.seed(seed); diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 828b61c..bd21a27 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -26,12 +26,17 @@ class WaveSystem; class Simulation { public: - explicit Simulation(const GameConfig& config, unsigned int seed = 0); + explicit Simulation(GameConfig config, unsigned int seed = 0); ~Simulation(); + const GameConfig& config() const; + // Reinitializes all simulation state as if constructed fresh. void reset(unsigned int seed = 0); + // Reloads config then reinitializes all simulation state. + void reset(GameConfig newConfig, unsigned int seed = 0); + // Advances the simulation by one tick. Tick order per architecture.md §Tick Order. void tick(); @@ -83,7 +88,7 @@ private: // Award a random blueprint drop (REQ-DEF-BLUEPRINT-DROP) and emit the event. void awardBlueprintDrop(); - const GameConfig& m_config; + GameConfig m_config; std::mt19937 m_rng; Tick m_currentTick; diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 7a608cc..2af7d88 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -8,15 +8,18 @@ #include #include "BuildButtonGrid.h" +#include "ConfigLoader.h" #include "GameWorldView.h" #include "HeaderBar.h" #include "SelectedBuildingPanel.h" #include "Simulation.h" #include "Tick.h" +#include "VisualsLoader.h" -MainWindow::MainWindow(Simulation* sim, const GameConfig* config, - const VisualsConfig* visuals, QWidget* parent) +MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent) : QWidget(parent) + , m_configDir(configDir) + , m_visuals(VisualsLoader::load(configDir + "/visuals.toml")) , m_sim(sim) { setWindowTitle("Dota Factory"); @@ -24,15 +27,15 @@ MainWindow::MainWindow(Simulation* sim, const GameConfig* config, m_headerBar = new HeaderBar(this); - m_gameWorldView = new GameWorldView(sim, config, visuals, this); + m_gameWorldView = new GameWorldView(sim, &sim->config(), &m_visuals, this); m_bottomPanel = new QWidget(this); QHBoxLayout* bottomLayout = new QHBoxLayout(m_bottomPanel); bottomLayout->setContentsMargins(0, 0, 0, 0); bottomLayout->setSpacing(0); - m_selectedBuildingPanel = new SelectedBuildingPanel(sim, config, m_bottomPanel); - m_buildButtonGrid = new BuildButtonGrid(config, m_bottomPanel); + m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->config(), m_bottomPanel); + m_buildButtonGrid = new BuildButtonGrid(&sim->config(), m_bottomPanel); bottomLayout->addWidget(m_selectedBuildingPanel, 1); bottomLayout->addWidget(m_buildButtonGrid, 1); @@ -128,7 +131,20 @@ void MainWindow::onEscapeMenuRequested() QAbstractButton* clicked = box.clickedButton(); if (clicked == restartBtn) { - m_sim->reset(); + try + { + GameConfig newConfig = ConfigLoader::loadFromDirectory(m_configDir); + VisualsConfig newVisuals = VisualsLoader::load(m_configDir + "/visuals.toml"); + m_visuals = std::move(newVisuals); + m_sim->reset(std::move(newConfig)); + } + catch (const std::exception& e) + { + QMessageBox::critical(this, "Config Error", + QString("Failed to reload config:\n%1").arg(e.what())); + m_gameWorldView->setGameSpeed(prevSpeed); + return; + } m_gameWorldView->resetForNewGame(); } else if (clicked == quitBtn) @@ -159,7 +175,19 @@ void MainWindow::onGameOver() if (box.clickedButton() == restartBtn) { - m_sim->reset(); + try + { + GameConfig newConfig = ConfigLoader::loadFromDirectory(m_configDir); + VisualsConfig newVisuals = VisualsLoader::load(m_configDir + "/visuals.toml"); + m_visuals = std::move(newVisuals); + m_sim->reset(std::move(newConfig)); + } + catch (const std::exception& e) + { + QMessageBox::critical(this, "Config Error", + QString("Failed to reload config:\n%1").arg(e.what())); + return; + } m_gameWorldView->resetForNewGame(); } else diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index 563c7fe..77df407 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -1,8 +1,9 @@ #pragma once +#include + #include -#include "GameConfig.h" #include "Tick.h" #include "VisualsConfig.h" @@ -18,8 +19,7 @@ class MainWindow : public QWidget Q_OBJECT public: - MainWindow(Simulation* sim, const GameConfig* config, - const VisualsConfig* visuals, QWidget* parent = nullptr); + MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent = nullptr); protected: void resizeEvent(QResizeEvent* event) override; @@ -32,6 +32,8 @@ private slots: private: void layoutPanels(); + std::string m_configDir; + VisualsConfig m_visuals; Simulation* m_sim; GameWorldView* m_gameWorldView; HeaderBar* m_headerBar;