Files
dota_factory/src/ui/MainWindow.cpp

273 lines
8.4 KiB
C++

#include "MainWindow.h"
#include <map>
#include <set>
#include <QApplication>
#include <QCloseEvent>
#include <QFile>
#include <QMessageBox>
#include <QPushButton>
#include <QResizeEvent>
#include <QVBoxLayout>
#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<const BuildingBlocksChangedEvent> event)
{
m_buildButtonGrid->updateAffordability(event->blocks);
}
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> 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<const EscapeMenuRequestedEvent> /*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<const LayoutDialogRequestedEvent> 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<std::string> unlockedModuleIds;
std::map<std::string, int> 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<const GameOverEvent> /*event*/)
{
const Tick tick = m_sim->currentTick();
const int totalSeconds = static_cast<int>(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();
}
}