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 "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<Simulation> sim = std::make_unique<Simulation>(config);
std::unique_ptr<Simulation> sim = std::make_unique<Simulation>(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();

View File

@@ -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;

View File

@@ -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<BuildingSystem>(
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<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_waveSystem = std::make_unique<WaveSystem>(config, m_rng);
m_combatSystem = std::make_unique<CombatSystem>(config);
m_waveSystem = std::make_unique<WaveSystem>(m_config, m_rng);
m_combatSystem = std::make_unique<CombatSystem>(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);

View File

@@ -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;

View File

@@ -8,15 +8,18 @@
#include <QVBoxLayout>
#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

View File

@@ -1,8 +1,9 @@
#pragma once
#include <string>
#include <QWidget>
#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;