#include "MainWindow.h" #include #include #include #include #include #include #include #include #include #include "BlueprintPanel.h" #include "BuildButtonGrid.h" #include "BuildingBlocksChangedEvent.h" #include "BuildingSystem.h" #include "ConfigLoader.h" #include "GameWorldView.h" #include "SchematicChoiceDialog.h" #include "HeaderBar.h" #include "SelectedBuildingPanel.h" #include "ShipLayoutBlueprintSerializer.h" #include "ShipLayoutDialog.h" #include "Simulation.h" #include "Tick.h" #include "VisualsLoader.h" 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(tr("Dota Factory")); resize(1280, 768); m_headerBar = new HeaderBar(this); m_gameWorldView = new GameWorldView(sim, &sim->config(), &m_visuals, this); m_sidePanel = new QWidget(this); QVBoxLayout* sideLayout = new QVBoxLayout(m_sidePanel); sideLayout->setContentsMargins(0, 0, 0, 0); sideLayout->setSpacing(0); m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->config(), m_sidePanel); m_buildButtonGrid = new BuildButtonGrid(&sim->config(), m_sidePanel); m_blueprintPanel = new BlueprintPanel(sim, &sim->config(), m_sidePanel); sideLayout->addWidget(m_selectedBuildingPanel, 1); sideLayout->addWidget(m_buildButtonGrid, 1); sideLayout->addWidget(m_blueprintPanel, 1); m_gameWorldView->setFocus(); connect(qApp, &QApplication::focusChanged, this, [this](QWidget*, QWidget* newWidget) { if (newWidget && newWidget != m_gameWorldView && !QApplication::activeModalWidget()) { m_gameWorldView->setFocus(); } }); // Load layout blueprints from disk. Missing file is silently ignored. const QString bpPath = QCoreApplication::applicationDirPath() + "/ship_layouts.toml"; QFile bpFile(bpPath); if (bpFile.open(QIODevice::ReadOnly | QIODevice::Text)) { try { m_layoutBlueprints = ShipLayoutBlueprintSerializer::deserialize( bpFile.readAll().toStdString()); } catch (const std::exception& e) { QMessageBox::critical(this, tr("Load Error"), tr("Failed to load ship_layouts.toml:\n%1").arg(e.what())); m_layoutBlueprints.clear(); } } registerForEvents(); } MainWindow::~MainWindow() { unregisterForEvents(); } void MainWindow::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); layoutPanels(); } void MainWindow::closeEvent(QCloseEvent* event) { const QString path = QCoreApplication::applicationDirPath() + "/ship_layouts.toml"; QFile file(path); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { try { const std::string content = ShipLayoutBlueprintSerializer::serialize(m_layoutBlueprints); file.write(QByteArray::fromStdString(content)); } catch (...) {} } QWidget::closeEvent(event); } void MainWindow::layoutPanels() { const int totalW = width(); const int totalH = height(); const int headerH = m_headerBar->sizeHint().height(); if (headerH <= 0) { return; } const int mainW = totalW * 75 / 100; const int sideW = totalW - mainW; m_headerBar->setGeometry(0, 0, mainW, headerH); m_gameWorldView->setGeometry(0, headerH, mainW, totalH - headerH); m_sidePanel->setGeometry(mainW, 0, sideW, totalH); } void MainWindow::handleEvent(std::shared_ptr event) { m_buildButtonGrid->updateAffordability(event->blocks); } void MainWindow::handleEvent(std::shared_ptr event) { const double prevSpeed = m_gameWorldView->gameSpeed(); m_gameWorldView->setGameSpeed(0.0); SchematicChoiceDialog dialog(event->choices, this); dialog.exec(); m_sim->applySchematicChoice(dialog.getChosenIndex()); m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); } void MainWindow::handleEvent(std::shared_ptr /*event*/) { const double prevSpeed = m_gameWorldView->gameSpeed(); m_gameWorldView->setGameSpeed(0.0); QMessageBox box(this); box.setWindowTitle(tr("Paused")); QPushButton* continueBtn = box.addButton(tr("Continue"), QMessageBox::AcceptRole); QPushButton* restartBtn = box.addButton(tr("Restart"), QMessageBox::ResetRole); QPushButton* quitBtn = box.addButton(tr("Quit"), QMessageBox::DestructiveRole); box.setEscapeButton(continueBtn); box.exec(); QAbstractButton* clicked = box.clickedButton(); if (clicked == restartBtn) { 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, tr("Config Error"), tr("Failed to reload config:\n%1").arg(e.what())); m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); return; } m_gameWorldView->resetForNewGame(); } else if (clicked == quitBtn) { close(); } else { m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); } } void MainWindow::handleEvent(std::shared_ptr event) { const double prevSpeed = m_gameWorldView->gameSpeed(); m_gameWorldView->setGameSpeed(0.0); const Building* b = m_sim->buildings().findBuilding(event->shipyardId); if (!b) { m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); return; } ShipLayoutConfig currentLayout; if (b->shipLayout.has_value()) { currentLayout = *b->shipLayout; } std::set unlockedModuleIds; std::map moduleLevels; for (const ModuleDef& def : m_sim->config().modules.modules) { if (m_sim->isModuleSchematicUnlocked(def.id)) { unlockedModuleIds.insert(def.id); } moduleLevels[def.id] = m_sim->moduleSchematicLevel(def.id); } ShipLayoutDialog dialog(&m_sim->config(), b->recipeId, currentLayout, m_layoutBlueprints, std::move(unlockedModuleIds), std::move(moduleLevels), m_gameWorldView->isDebugDrawEnabled(), this); if (dialog.exec() == QDialog::Accepted && dialog.result().has_value()) { m_sim->buildings().setShipLayout(event->shipyardId, *dialog.result()); } m_gameWorldView->setGameSpeed(prevSpeed); m_gameWorldView->resetFrameTimer(); } void MainWindow::handleEvent(std::shared_ptr /*event*/) { const Tick tick = m_sim->currentTick(); const int totalSeconds = static_cast(ticksToSeconds(tick)); const int minutes = totalSeconds / 60; const int seconds = totalSeconds % 60; QMessageBox box(this); box.setWindowTitle(tr("Game Over")); box.setText(tr("HQ destroyed!\nSurvival time: %1:%2") .arg(minutes, 2, 10, QChar('0')) .arg(seconds, 2, 10, QChar('0'))); QPushButton* restartBtn = box.addButton(tr("Restart"), QMessageBox::AcceptRole); box.addButton(tr("Quit"), QMessageBox::RejectRole); box.exec(); if (box.clickedButton() == restartBtn) { 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, tr("Config Error"), tr("Failed to reload config:\n%1").arg(e.what())); return; } m_gameWorldView->resetForNewGame(); } else { close(); } }