Files
dota_factory/src/ui/BlueprintLibrary.cpp

202 lines
6.9 KiB
C++

#include "BlueprintLibrary.h"
#include <cstddef>
#include <utility>
#include <QCoreApplication>
#include <QFile>
#include <QMessageBox>
#include <QObject>
#include <QStringList>
#include "BlueprintPlacementRequestedEvent.h"
#include "BlueprintSerializer.h"
#include "EventManager.h"
#include "ExitBlueprintModeRequestedEvent.h"
#include "BuildingConfig.h"
#include "Simulation.h"
BlueprintLibrary::BlueprintLibrary(Simulation* sim, const GameConfig* config,
QWidget* dialogParent)
: m_sim(sim)
, m_config(config)
, m_dialogParent(dialogParent)
{
loadFromDisk();
registerForEvents();
}
BlueprintLibrary::~BlueprintLibrary()
{
saveToDisk();
unregisterForEvents();
}
bool BlueprintLibrary::getCanCaptureSelection() const
{
// A construction site counts the same as an operational building (REQ-UI-BLUEPRINT-CREATE).
return selectionHasPlaceableBuilding(*m_sim, m_selectedBuildingIds);
}
void BlueprintLibrary::saveSelectionAs(const QString& name)
{
Blueprint blueprint = createBlueprintFromSelection();
if (blueprint.buildings.empty()) { return; }
blueprint.name = name;
m_blueprints.push_back(std::move(blueprint));
}
const std::vector<Blueprint>& BlueprintLibrary::getBlueprints() const
{
return m_blueprints;
}
QString BlueprintLibrary::getContentsSummary(int index) const
{
if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return QString(); }
QStringList entries;
for (const BlueprintContentEntry& entry : summarizeBlueprintContents(
m_blueprints[static_cast<std::size_t>(index)], m_config->buildings))
{
// The same "<type> x <count>" notation the building multi-selection summary
// uses (REQ-UI-MULTI-SELECTION), which also sidesteps plural forms.
entries << QObject::tr("%1 x %2")
.arg(QString::fromStdString(entry.buildingName))
.arg(entry.count);
}
return entries.join(QStringLiteral(", "));
}
int BlueprintLibrary::getCost(int index) const
{
if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return 0; }
return computeBlueprintCost(m_blueprints[static_cast<std::size_t>(index)],
m_config->buildings);
}
bool BlueprintLibrary::getCanAfford(int index) const
{
return m_sim->getBuildingBlocksStock() >= getCost(index);
}
void BlueprintLibrary::remove(int index)
{
if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return; }
if (m_activeIndex == index)
{
m_activeIndex = std::nullopt;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ExitBlueprintModeRequestedEvent>());
}
else if (m_activeIndex.has_value() && *m_activeIndex > index)
{
--*m_activeIndex;
}
m_blueprints.erase(m_blueprints.begin() + index);
}
void BlueprintLibrary::beginPlacement(int index)
{
if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return; }
m_activeIndex = index;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintPlacementRequestedEvent>(
m_blueprints[static_cast<std::size_t>(index)]));
}
void BlueprintLibrary::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
{
m_selectedBuildingIds = event->ids;
}
void BlueprintLibrary::handleEvent(std::shared_ptr<const BlueprintModeExitedEvent> /*event*/)
{
// Only the saved-blueprint index is cleared. A temporary blueprint deliberately
// survives its placement mode so V can re-enter it (REQ-UI-BLUEPRINT-TEMP).
m_activeIndex = std::nullopt;
}
void BlueprintLibrary::handleEvent(
std::shared_ptr<const TemporaryBlueprintCaptureRequestedEvent> /*event*/)
{
// C (REQ-UI-BLUEPRINT-TEMP): capture the current selection and enter placement mode
// for it, without naming it, listing it, or persisting it. If nothing player-placeable
// is selected, do nothing at all -- in particular, keep the previous temporary
// blueprint, which V can still place.
Blueprint blueprint = createBlueprintFromSelection();
if (blueprint.buildings.empty()) { return; }
m_temporaryBlueprint = std::move(blueprint);
// No saved blueprint is active while a temporary one is being placed.
m_activeIndex = std::nullopt;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintPlacementRequestedEvent>(*m_temporaryBlueprint));
}
void BlueprintLibrary::handleEvent(
std::shared_ptr<const TemporaryBlueprintPlaceRequestedEvent> /*event*/)
{
// V (REQ-UI-BLUEPRINT-TEMP): re-enter placement mode for the temporary blueprint,
// capturing nothing. With none captured, nothing happens -- no mode is entered and
// whichever mode is active is left alone. Affordability is not checked here, matching
// C; cost is enforced at placement (REQ-UI-BLUEPRINT-PLACE). The blueprint is copied,
// not moved: it stays available for the next V.
if (!m_temporaryBlueprint.has_value()) { return; }
m_activeIndex = std::nullopt;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintPlacementRequestedEvent>(*m_temporaryBlueprint));
}
void BlueprintLibrary::handleEvent(std::shared_ptr<const GameResetEvent> /*event*/)
{
// A restart begins a new run, and the temporary blueprint belongs to the old one
// (REQ-UI-BLUEPRINT-TEMP). The saved blueprints are not run state and stay.
m_temporaryBlueprint = std::nullopt;
}
Blueprint BlueprintLibrary::createBlueprintFromSelection() const
{
// Capture is shared, testable logic in lib/sim: it resolves each selected id as an
// operational building or a construction site alike (REQ-UI-BLUEPRINT-CREATE,
// REQ-UI-BLUEPRINT-STORAGE).
return captureBlueprintFromSelection(*m_sim, m_selectedBuildingIds);
}
void BlueprintLibrary::loadFromDisk()
{
// Load at startup (REQ-UI-BLUEPRINT-LOAD). Missing file: start empty, no error.
const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; }
try
{
m_blueprints = BlueprintSerializer::deserialize(file.readAll().toStdString());
}
catch (const std::exception& e)
{
QMessageBox::critical(m_dialogParent, QObject::tr("Load Failed"),
QObject::tr("Failed to load blueprints:\n%1").arg(e.what()));
m_blueprints.clear();
}
}
void BlueprintLibrary::saveToDisk() const
{
// Persist on shutdown; write errors are silently ignored (REQ-UI-BLUEPRINT-SAVE).
const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; }
try
{
const std::string content = BlueprintSerializer::serialize(m_blueprints);
file.write(QByteArray::fromStdString(content));
}
catch (...) {}
}