Compare commits

..

4 Commits

15 changed files with 434 additions and 84 deletions

View File

@@ -11,6 +11,8 @@ Config files use the TOML format. The following config files drive game paramete
- **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level. - **stations.toml** — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level.
- **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it. - **visuals.toml** — rendering-only config (not game parameters): fill and outline colors and glyphs for every building type, item type, ship role, and station type; beam color and width; overlay and toast colors. Loaded by the UI at startup; the simulation does not read it.
- REQ-CFG-RELOAD: When the player triggers a Restart (REQ-UI-GAME-MENU), all config files are reloaded from disk before the simulation is reset to its initial state. Formula strings are recompiled at that point. This allows config edits made while the application is running to take effect without a full application restart.
### Surface Mask Format ### Surface Mask Format
Buildings in buildings.toml define a `surface_mask` — a list of strings that describes the building's tile footprint and output port(s). Each character occupies one cell in the grid: Buildings in buildings.toml define a `surface_mask` — a list of strings that describes the building's tile footprint and output port(s). Each character occupies one cell in the grid:

View File

@@ -206,7 +206,7 @@ set_property(TARGET ${TARGET_TEST_NAME} PROPERTY INCLUDE_DIRECTORIES
"${LIB_INCLUDE_PATH}" "${LIB_INCLUDE_PATH}"
) )
target_compile_definitions(${TARGET_TEST_NAME} PRIVATE target_compile_definitions(${TARGET_TEST_NAME} PRIVATE
DOTA_FACTORY_CONFIG_DIR="${CMAKE_SOURCE_DIR}/bin/config" DOTA_FACTORY_CONFIG_DIR="${CMAKE_SOURCE_DIR}/src/test/config"
) )
target_link_libraries(${TARGET_TEST_NAME} ${TARGET_LIB_NAME}) target_link_libraries(${TARGET_TEST_NAME} ${TARGET_LIB_NAME})

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

@@ -202,28 +202,6 @@ EntityId BuildingSystem::place(BuildingType type, QPoint anchor,
{ {
const EntityId id = m_allocateId(); const EntityId id = m_allocateId();
if (type == BuildingType::Belt)
{
m_belts.placeBelt(anchor, rotation);
m_tileOccupancy[{anchor.x(), anchor.y()}] = id;
m_beltEntities[id] = BeltEntry{anchor, BuildingType::Belt, rotation, rotation};
return id;
}
if (type == BuildingType::Splitter)
{
const BuildingDef* def = findBuildingDef(type);
assert(def != nullptr);
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
assert(mask.outputPorts.size() >= 2);
const Rotation outA = mask.outputPorts[0].direction;
const Rotation outB = mask.outputPorts[1].direction;
m_belts.placeSplitter(anchor, outA, outB);
m_tileOccupancy[{anchor.x(), anchor.y()}] = id;
m_beltEntities[id] = BeltEntry{anchor, BuildingType::Splitter, outA, outB};
return id;
}
const BuildingDef* def = findBuildingDef(type); const BuildingDef* def = findBuildingDef(type);
assert(def != nullptr); assert(def != nullptr);
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation); const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, rotation);
@@ -403,7 +381,25 @@ void BuildingSystem::tickConstruction(Tick currentTick)
return; return;
} }
// Promote construction site to operational building. // Promote construction site — belts/splitters go into BeltSystem, others become Buildings.
if (front.type == BuildingType::Belt)
{
m_belts.placeBelt(front.anchor, front.rotation);
m_beltEntities[front.id] = BeltEntry{front.anchor, BuildingType::Belt, front.rotation, front.rotation};
}
else if (front.type == BuildingType::Splitter)
{
const BuildingDef* def = findBuildingDef(front.type);
assert(def != nullptr);
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, front.rotation);
assert(mask.outputPorts.size() >= 2);
const Rotation outA = mask.outputPorts[0].direction;
const Rotation outB = mask.outputPorts[1].direction;
m_belts.placeSplitter(front.anchor, outA, outB);
m_beltEntities[front.id] = BeltEntry{front.anchor, BuildingType::Splitter, outA, outB};
}
else
{
const BuildingDef* def = findBuildingDef(front.type); const BuildingDef* def = findBuildingDef(front.type);
const ParsedSurfaceMask mask = parseSurfaceMask( const ParsedSurfaceMask mask = parseSurfaceMask(
def ? def->surfaceMask : std::vector<std::string>{}, def ? def->surfaceMask : std::vector<std::string>{},
@@ -449,6 +445,8 @@ void BuildingSystem::tickConstruction(Tick currentTick)
} }
m_buildings.push_back(std::move(building)); m_buildings.push_back(std::move(building));
}
m_constructionQueue.pop_front(); m_constructionQueue.pop_front();
// Start next queued site if present. // Start next queued site if present.

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

@@ -92,7 +92,8 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
REQUIRE_FALSE(bs.isTileOccupied(QPoint(1, 1))); REQUIRE_FALSE(bs.isTileOccupied(QPoint(1, 1)));
} }
TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem", "[building]") TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after construction",
"[building]")
{ {
const GameConfig cfg = loadConfig(); const GameConfig cfg = loadConfig();
BeltSystem belts(cfg.world.beltSpeedTilesPerSecond); BeltSystem belts(cfg.world.beltSpeedTilesPerSecond);
@@ -107,6 +108,13 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem", "[build
bs.place(BuildingType::Belt, QPoint(5, 5), Rotation::East, 0); bs.place(BuildingType::Belt, QPoint(5, 5), Rotation::East, 0);
// Belt is queued — not yet in BeltSystem.
REQUIRE_FALSE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
// Complete construction (1 s).
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"))); REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
REQUIRE(bs.allBuildings().empty()); // belts do not create Building instances REQUIRE(bs.allBuildings().empty()); // belts do not create Building instances
} }

View File

@@ -0,0 +1,75 @@
[[building]]
id = "belt"
cost = 2
player_placeable = true
construction_time_seconds = 1
surface_mask = ["A>"]
[[building]]
id = "splitter"
cost = 3
player_placeable = true
construction_time_seconds = 1
surface_mask = ["<A>"]
[[building]]
id = "miner"
cost = 15
player_placeable = true
construction_time_seconds = 10
surface_mask = [
"AA",
"A>",
]
[[building]]
id = "smelter"
cost = 20
player_placeable = true
construction_time_seconds = 15
surface_mask = [
"AA ",
"AA>",
]
[[building]]
id = "assembler"
cost = 35
player_placeable = true
construction_time_seconds = 20
surface_mask = [
"AAA ",
"AAA>",
"AAA ",
]
[[building]]
id = "reprocessing_plant"
cost = 40
player_placeable = true
construction_time_seconds = 25
surface_mask = [
"AAA ",
"AAA>",
"AAA ",
]
[[building]]
id = "shipyard"
cost = 60
player_placeable = true
construction_time_seconds = 30
surface_mask = [
"AAAS>",
"AAAS ",
]
[[building]]
id = "salvage_bay"
cost = 25
player_placeable = true
construction_time_seconds = 15
surface_mask = [
"SAA",
"SAA>",
]

View File

@@ -0,0 +1,62 @@
[[recipe]]
id = "mine_iron_ore"
building = "miner"
inputs = []
outputs = [{item = "iron_ore", amount = 1}]
duration_seconds = 1.0
[[recipe]]
id = "mine_copper_ore"
building = "miner"
inputs = []
outputs = [{item = "copper_ore", amount = 1}]
duration_seconds = 1.5
[[recipe]]
id = "iron_ingot"
building = "smelter"
inputs = [{item = "iron_ore", amount = 2}]
outputs = [{item = "iron_ingot", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "copper_ingot"
building = "smelter"
inputs = [{item = "copper_ore", amount = 2}]
outputs = [{item = "copper_ingot", amount = 1}]
duration_seconds = 2.5
[[recipe]]
id = "circuit_board"
building = "assembler"
inputs = [{item = "iron_ingot", amount = 3}, {item = "copper_ingot", amount = 2}]
outputs = [{item = "circuit_board", amount = 1}]
duration_seconds = 5.0
[[recipe]]
id = "building_blocks"
building = "assembler"
inputs = [{item = "iron_ingot", amount = 4}]
outputs = [{item = "building_block", amount = 10}]
duration_seconds = 4.0
[[recipe]]
id = "reprocessing_cycle"
building = "reprocessing_plant"
inputs = [{item = "scrap", amount = 5}]
duration_seconds = 3.0
[[recipe.outputs]]
item = "iron_ingot"
amount = 2
probability = 0.6
[[recipe.outputs]]
item = "circuit_board"
amount = 1
probability = 0.3
[[recipe.outputs]]
item = "advanced_alloy"
amount = 1
probability = 0.1

104
src/test/config/ships.toml Normal file
View File

@@ -0,0 +1,104 @@
[[ship]]
id = "interceptor"
available_from_start = true
[ship.blueprint]
materials = [{item = "iron_ingot", amount = 3}, {item = "circuit_board", amount = 1}]
player_production_level = 3
production_time_seconds = 10
[ship.threat]
cost_formula = "5 + 1*x"
[ship.health]
hp_formula = "40 + 5*x"
[ship.movement]
speed_formula = "200 + 5*x"
[ship.combat]
damage_formula = "10 + 2*x"
attack_range_formula = "150"
attack_rate_formula = "2.0"
[ship.loot]
scrap_drop = 2
[[ship]]
id = "destroyer"
available_from_start = true
[ship.blueprint]
materials = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 2}]
player_production_level = 5
production_time_seconds = 20
[ship.threat]
cost_formula = "10 + 2*x"
[ship.health]
hp_formula = "120 + 15*x"
[ship.movement]
speed_formula = "120"
[ship.combat]
damage_formula = "12 + 2*x"
attack_range_formula = "250"
attack_rate_formula = "1.0"
[ship.loot]
scrap_drop = 4
[[ship]]
id = "salvage_ship"
available_from_start = true
[ship.blueprint]
materials = [{item = "iron_ingot", amount = 4}]
player_production_level = 3
production_time_seconds = 10
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "40 + 4*x"
[ship.movement]
speed_formula = "110"
[ship.salvage]
collection_range = 50
cargo_capacity = 10
[ship.loot]
scrap_drop = 2
[[ship]]
id = "repair_ship"
available_from_start = false
[ship.blueprint]
materials = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
player_production_level = 3
production_time_seconds = 15
[ship.threat]
cost_formula = "0"
[ship.health]
hp_formula = "60 + 5*x"
[ship.movement]
speed_formula = "130"
[ship.repair]
repair_rate_formula = "5 + x"
repair_range_formula = "80"
[ship.loot]
scrap_drop = 2

View File

@@ -0,0 +1,30 @@
[hq]
surface_mask = [
"AAA",
"AAA",
"AAA",
]
hp_formula = "1000"
[player_station]
surface_mask = [
"SS",
"SS",
]
level = 5
hp_formula = "300 + 40*x"
damage_formula = "5 + 4*x"
range_formula = "300 + 20*x"
fire_rate_formula = "0.5 + 0.2*x"
scrap_drop_formula = "x"
[enemy_station]
surface_mask = [
"SS",
"SS",
]
hp_formula = "300 + 150*x"
damage_formula = "20 + 10*x"
range_formula = "350 + 20*x"
fire_rate_formula = "1.0 + 0.2*x"
scrap_drop_formula = "10 + 5*x"

View File

@@ -0,0 +1,27 @@
[world]
height_tiles = 60
refund_percentage = 75
starting_building_blocks = 100
scrap_despawn_seconds = 30
belt_speed_tiles_per_second = 2
[regions]
asteroid_width = 40
player_buffer_width = 10
contest_zone_width = 30
enemy_buffer_width = 15
[expansion]
columns_per_expansion = 10
cost_building_blocks = 200
[push]
push_expand_columns = 20
scaling_factor = 1.2
[waves]
threat_rate_formula = "1*x - 30"
ship_level_formula = "1 + x / 120"
gap_min_seconds = 15
gap_max_seconds = 45
spawn_duration_seconds = 10

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;