implement load config on game restart

This commit is contained in:
2026-04-22 22:53:56 +02:00
parent 1b218941bd
commit 78f746d352
6 changed files with 71 additions and 27 deletions

View File

@@ -9,7 +9,6 @@
#include "LogManager.h" #include "LogManager.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "Simulation.h" #include "Simulation.h"
#include "VisualsLoader.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@@ -33,10 +32,9 @@ int main(int argc, char *argv[])
} }
GameConfig config = ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR); GameConfig config = ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR);
VisualsConfig visuals = VisualsLoader::load(std::string(DOTA_FACTORY_CONFIG_DIR) + "/visuals.toml"); std::unique_ptr<Simulation> sim = std::make_unique<Simulation>(std::move(config));
std::unique_ptr<Simulation> sim = std::make_unique<Simulation>(config);
MainWindow window(sim.get(), &config, &visuals); MainWindow window(sim.get(), std::string(DOTA_FACTORY_CONFIG_DIR));
window.show(); window.show();
const int ret = application.exec(); const int ret = application.exec();

View File

@@ -6,8 +6,8 @@
#include "ShipsConfig.h" #include "ShipsConfig.h"
#include "StationsConfig.h" #include "StationsConfig.h"
// Aggregate of all five simulation config files, loaded once at startup and // Aggregate of all five simulation config files. Loaded at startup and reloaded
// immutable for the rest of the game. See architecture.md "Config Loading". // from disk on each game restart (REQ-CFG-RELOAD). See architecture.md "Config Loading".
struct GameConfig struct GameConfig
{ {
WorldConfig world; WorldConfig world;

View File

@@ -9,23 +9,23 @@
#include "SurfaceMask.h" #include "SurfaceMask.h"
#include "WaveSystem.h" #include "WaveSystem.h"
Simulation::Simulation(const GameConfig& config, unsigned int seed) Simulation::Simulation(GameConfig config, unsigned int seed)
: m_config(config) : m_config(std::move(config))
, m_rng(seed) , m_rng(seed)
, m_currentTick(0) , m_currentTick(0)
, m_nextId(1) , m_nextId(1)
, m_buildingBlocksStock(config.world.startingBuildingBlocks) , m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
, m_gameOver(false) , m_gameOver(false)
, m_hqId(kInvalidEntityId) , m_hqId(kInvalidEntityId)
, m_playerStation1Id(kInvalidEntityId) , m_playerStation1Id(kInvalidEntityId)
, m_playerStation2Id(kInvalidEntityId) , m_playerStation2Id(kInvalidEntityId)
, m_beltSystem(config.world.beltSpeedTilesPerSecond) , m_beltSystem(m_config.world.beltSpeedTilesPerSecond)
{ {
m_currentEnemyStationIds[0] = kInvalidEntityId; m_currentEnemyStationIds[0] = kInvalidEntityId;
m_currentEnemyStationIds[1] = kInvalidEntityId; m_currentEnemyStationIds[1] = kInvalidEntityId;
m_buildingSystem = std::make_unique<BuildingSystem>( m_buildingSystem = std::make_unique<BuildingSystem>(
config, m_config,
m_beltSystem, m_beltSystem,
[this]() { return allocateId(); }, [this]() { return allocateId(); },
[this](int amount) { m_buildingBlocksStock += amount; }, [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_shipSystem->spawn(id, it->second.level, pos, /*isEnemy=*/false);
}, },
m_rng); m_rng);
m_shipSystem = std::make_unique<ShipSystem>(config, [this]() { return allocateId(); }); m_shipSystem = std::make_unique<ShipSystem>(m_config, [this]() { return allocateId(); });
m_scrapSystem = std::make_unique<ScrapSystem>([this]() { return allocateId(); }); m_scrapSystem = std::make_unique<ScrapSystem>([this]() { return allocateId(); });
m_waveSystem = std::make_unique<WaveSystem>(config, m_rng); m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
m_combatSystem = std::make_unique<CombatSystem>(config); m_combatSystem = std::make_unique<CombatSystem>(m_config);
// Initialize blueprint unlock state. // Initialize blueprint unlock state.
for (const ShipDef& def : config.ships.ships) for (const ShipDef& def : m_config.ships.ships)
{ {
BlueprintState state; BlueprintState state;
state.unlocked = def.availableFromStart; state.unlocked = def.availableFromStart;
@@ -58,6 +58,17 @@ Simulation::Simulation(const GameConfig& config, unsigned int seed)
Simulation::~Simulation() = default; 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) void Simulation::reset(unsigned int seed)
{ {
m_rng.seed(seed); m_rng.seed(seed);

View File

@@ -26,12 +26,17 @@ class WaveSystem;
class Simulation class Simulation
{ {
public: public:
explicit Simulation(const GameConfig& config, unsigned int seed = 0); explicit Simulation(GameConfig config, unsigned int seed = 0);
~Simulation(); ~Simulation();
const GameConfig& config() const;
// Reinitializes all simulation state as if constructed fresh. // Reinitializes all simulation state as if constructed fresh.
void reset(unsigned int seed = 0); 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. // Advances the simulation by one tick. Tick order per architecture.md §Tick Order.
void tick(); void tick();
@@ -83,7 +88,7 @@ private:
// Award a random blueprint drop (REQ-DEF-BLUEPRINT-DROP) and emit the event. // Award a random blueprint drop (REQ-DEF-BLUEPRINT-DROP) and emit the event.
void awardBlueprintDrop(); void awardBlueprintDrop();
const GameConfig& m_config; GameConfig m_config;
std::mt19937 m_rng; std::mt19937 m_rng;
Tick m_currentTick; Tick m_currentTick;

View File

@@ -8,15 +8,18 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include "BuildButtonGrid.h" #include "BuildButtonGrid.h"
#include "ConfigLoader.h"
#include "GameWorldView.h" #include "GameWorldView.h"
#include "HeaderBar.h" #include "HeaderBar.h"
#include "SelectedBuildingPanel.h" #include "SelectedBuildingPanel.h"
#include "Simulation.h" #include "Simulation.h"
#include "Tick.h" #include "Tick.h"
#include "VisualsLoader.h"
MainWindow::MainWindow(Simulation* sim, const GameConfig* config, MainWindow::MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent)
const VisualsConfig* visuals, QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_configDir(configDir)
, m_visuals(VisualsLoader::load(configDir + "/visuals.toml"))
, m_sim(sim) , m_sim(sim)
{ {
setWindowTitle("Dota Factory"); setWindowTitle("Dota Factory");
@@ -24,15 +27,15 @@ MainWindow::MainWindow(Simulation* sim, const GameConfig* config,
m_headerBar = new HeaderBar(this); 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); m_bottomPanel = new QWidget(this);
QHBoxLayout* bottomLayout = new QHBoxLayout(m_bottomPanel); QHBoxLayout* bottomLayout = new QHBoxLayout(m_bottomPanel);
bottomLayout->setContentsMargins(0, 0, 0, 0); bottomLayout->setContentsMargins(0, 0, 0, 0);
bottomLayout->setSpacing(0); bottomLayout->setSpacing(0);
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, config, m_bottomPanel); m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->config(), m_bottomPanel);
m_buildButtonGrid = new BuildButtonGrid(config, m_bottomPanel); m_buildButtonGrid = new BuildButtonGrid(&sim->config(), m_bottomPanel);
bottomLayout->addWidget(m_selectedBuildingPanel, 1); bottomLayout->addWidget(m_selectedBuildingPanel, 1);
bottomLayout->addWidget(m_buildButtonGrid, 1); bottomLayout->addWidget(m_buildButtonGrid, 1);
@@ -128,7 +131,20 @@ void MainWindow::onEscapeMenuRequested()
QAbstractButton* clicked = box.clickedButton(); QAbstractButton* clicked = box.clickedButton();
if (clicked == restartBtn) 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(); m_gameWorldView->resetForNewGame();
} }
else if (clicked == quitBtn) else if (clicked == quitBtn)
@@ -159,7 +175,19 @@ void MainWindow::onGameOver()
if (box.clickedButton() == restartBtn) 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(); m_gameWorldView->resetForNewGame();
} }
else else

View File

@@ -1,8 +1,9 @@
#pragma once #pragma once
#include <string>
#include <QWidget> #include <QWidget>
#include "GameConfig.h"
#include "Tick.h" #include "Tick.h"
#include "VisualsConfig.h" #include "VisualsConfig.h"
@@ -18,8 +19,7 @@ class MainWindow : public QWidget
Q_OBJECT Q_OBJECT
public: public:
MainWindow(Simulation* sim, const GameConfig* config, MainWindow(Simulation* sim, const std::string& configDir, QWidget* parent = nullptr);
const VisualsConfig* visuals, QWidget* parent = nullptr);
protected: protected:
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
@@ -32,6 +32,8 @@ private slots:
private: private:
void layoutPanels(); void layoutPanels();
std::string m_configDir;
VisualsConfig m_visuals;
Simulation* m_sim; Simulation* m_sim;
GameWorldView* m_gameWorldView; GameWorldView* m_gameWorldView;
HeaderBar* m_headerBar; HeaderBar* m_headerBar;