deselect build tool when it becomes unaffordable and fix stale enabled button
This commit is contained in:
@@ -97,6 +97,12 @@ All UI interactions — building selection, builder/blueprint mode transitions,
|
|||||||
|
|
||||||
Bidirectional interactions use separate request/notification event types to avoid infinite recursion (e.g., `ExitBuilderModeRequestedEvent` from `BuildButtonGrid` → `GameWorldView`, vs. `BuilderModeExitedEvent` from `GameWorldView` → `BuildButtonGrid`).
|
Bidirectional interactions use separate request/notification event types to avoid infinite recursion (e.g., `ExitBuilderModeRequestedEvent` from `BuildButtonGrid` → `GameWorldView`, vs. `BuilderModeExitedEvent` from `GameWorldView` → `BuildButtonGrid`).
|
||||||
|
|
||||||
|
### Reading Simulation State
|
||||||
|
|
||||||
|
The simulation is the single source of truth for every game value (building block stock, expansion cost, threat level, tick, etc.). A UI widget that needs such a value holds the `Simulation*` it was constructed with and **pulls the value on demand** via the corresponding getter (e.g., `m_sim->getBuildingBlocksStock()`), rather than caching its own copy.
|
||||||
|
|
||||||
|
State-change events (e.g., `BuildingBlocksChangedEvent`) are treated as *refresh signals*, not as carriers of truth: a widget subscribes to the event to learn *when* the value changed and then re-reads it from the simulation to learn *what* it now is. The value carried in the event payload is not authoritative and should not be stored. This keeps a single copy of each value and avoids stale-cache bugs (a widget acting on a value that has since moved on because nothing refreshed its local copy).
|
||||||
|
|
||||||
## Tick Order
|
## Tick Order
|
||||||
|
|
||||||
Within a single simulation tick, subsystems run in this fixed order. The order is load-bearing for determinism and for avoiding one-tick-delay artifacts (e.g., items landing on a belt but not advancing in the same tick).
|
Within a single simulation tick, subsystems run in this fixed order. The order is load-bearing for determinism and for avoiding one-tick-delay artifacts (e.g., items landing on a belt but not advancing in the same tick).
|
||||||
|
|||||||
@@ -12,12 +12,13 @@
|
|||||||
#include "DisplayName.h"
|
#include "DisplayName.h"
|
||||||
#include "EventManager.h"
|
#include "EventManager.h"
|
||||||
#include "ExitBuilderModeRequestedEvent.h"
|
#include "ExitBuilderModeRequestedEvent.h"
|
||||||
|
#include "Simulation.h"
|
||||||
|
|
||||||
|
|
||||||
BuildButtonGrid::BuildButtonGrid(const GameConfig* config, QWidget* parent)
|
BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
, m_sim(sim)
|
||||||
, m_config(config)
|
, m_config(config)
|
||||||
, m_activeIndex(-1)
|
|
||||||
{
|
{
|
||||||
QGridLayout* layout = new QGridLayout(this);
|
QGridLayout* layout = new QGridLayout(this);
|
||||||
layout->setSpacing(4);
|
layout->setSpacing(4);
|
||||||
@@ -79,24 +80,42 @@ BuildButtonGrid::~BuildButtonGrid()
|
|||||||
unregisterForEvents();
|
unregisterForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildButtonGrid::updateAffordability(int buildingBlocks)
|
void BuildButtonGrid::updateAffordability()
|
||||||
{
|
{
|
||||||
|
const int buildingBlocks = m_sim->getBuildingBlocksStock();
|
||||||
|
|
||||||
|
// If the currently selected tool can no longer be afforded, exit builder mode
|
||||||
|
// before recomputing button states so it does not stay selected. Clearing the
|
||||||
|
// active index first lets the loop below disable the now-unaffordable button.
|
||||||
|
if (m_activeIndex)
|
||||||
|
{
|
||||||
|
const BuildingType activeType = m_types[*m_activeIndex];
|
||||||
|
const std::map<BuildingType, int>::const_iterator it = m_costs.find(activeType);
|
||||||
|
const int cost = (it != m_costs.end()) ? it->second : 0;
|
||||||
|
if (buildingBlocks < cost)
|
||||||
|
{
|
||||||
|
clearActiveButton();
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<ExitBuilderModeRequestedEvent>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t i = 0; i < m_buttons.size(); ++i)
|
for (std::size_t i = 0; i < m_buttons.size(); ++i)
|
||||||
{
|
{
|
||||||
const BuildingType type = m_types[i];
|
const BuildingType type = m_types[i];
|
||||||
const std::map<BuildingType, int>::const_iterator it = m_costs.find(type);
|
const std::map<BuildingType, int>::const_iterator it = m_costs.find(type);
|
||||||
const int cost = (it != m_costs.end()) ? it->second : 0;
|
const int cost = (it != m_costs.end()) ? it->second : 0;
|
||||||
m_buttons[i]->setEnabled(buildingBlocks >= cost || m_activeIndex == static_cast<int>(i));
|
m_buttons[i]->setEnabled(buildingBlocks >= cost || m_activeIndex == i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildButtonGrid::clearActiveButton()
|
void BuildButtonGrid::clearActiveButton()
|
||||||
{
|
{
|
||||||
if (m_activeIndex >= 0 && m_activeIndex < static_cast<int>(m_buttons.size()))
|
if (m_activeIndex)
|
||||||
{
|
{
|
||||||
m_buttons[static_cast<std::size_t>(m_activeIndex)]->setChecked(false);
|
m_buttons[*m_activeIndex]->setChecked(false);
|
||||||
}
|
}
|
||||||
m_activeIndex = -1;
|
m_activeIndex.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildButtonGrid::onBuildButton(int index)
|
void BuildButtonGrid::onBuildButton(int index)
|
||||||
@@ -105,8 +124,9 @@ void BuildButtonGrid::onBuildButton(int index)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const std::size_t idx = static_cast<std::size_t>(index);
|
||||||
|
|
||||||
if (m_activeIndex == index)
|
if (m_activeIndex == idx)
|
||||||
{
|
{
|
||||||
clearActiveButton();
|
clearActiveButton();
|
||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
@@ -114,15 +134,15 @@ void BuildButtonGrid::onBuildButton(int index)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_activeIndex >= 0 && m_activeIndex < static_cast<int>(m_buttons.size()))
|
if (m_activeIndex)
|
||||||
{
|
{
|
||||||
m_buttons[static_cast<std::size_t>(m_activeIndex)]->setChecked(false);
|
m_buttons[*m_activeIndex]->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_activeIndex = index;
|
m_activeIndex = idx;
|
||||||
m_buttons[static_cast<std::size_t>(index)]->setChecked(true);
|
m_buttons[idx]->setChecked(true);
|
||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<BuildingTypeSelectedEvent>(m_types[static_cast<std::size_t>(index)]));
|
std::make_shared<BuildingTypeSelectedEvent>(m_types[idx]));
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent> /*event*/)
|
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent> /*event*/)
|
||||||
@@ -130,6 +150,11 @@ void BuildButtonGrid::handleEvent(std::shared_ptr<const BuilderModeExitedEvent>
|
|||||||
clearActiveButton();
|
clearActiveButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuildButtonGrid::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> /*event*/)
|
||||||
|
{
|
||||||
|
updateAffordability();
|
||||||
|
}
|
||||||
|
|
||||||
void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event)
|
void BuildButtonGrid::handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event)
|
||||||
{
|
{
|
||||||
m_demolishButton->setChecked(event->active);
|
m_demolishButton->setChecked(event->active);
|
||||||
|
|||||||
@@ -1,46 +1,56 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "BuilderModeExitedEvent.h"
|
#include "BuilderModeExitedEvent.h"
|
||||||
#include "BuildHotkeyPressedEvent.h"
|
#include "BuildHotkeyPressedEvent.h"
|
||||||
|
#include "BuildingBlocksChangedEvent.h"
|
||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "DemolishModeChangedEvent.h"
|
#include "DemolishModeChangedEvent.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
class Simulation;
|
||||||
|
|
||||||
class BuildButtonGrid : public QWidget,
|
class BuildButtonGrid : public QWidget,
|
||||||
public CombinedEventHandler<BuilderModeExitedEvent,
|
public CombinedEventHandler<BuilderModeExitedEvent,
|
||||||
DemolishModeChangedEvent,
|
DemolishModeChangedEvent,
|
||||||
BuildHotkeyPressedEvent>
|
BuildHotkeyPressedEvent,
|
||||||
|
BuildingBlocksChangedEvent>
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BuildButtonGrid(const GameConfig* config, QWidget* parent = nullptr);
|
BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr);
|
||||||
~BuildButtonGrid() override;
|
~BuildButtonGrid() override;
|
||||||
|
|
||||||
void updateAffordability(int buildingBlocks);
|
|
||||||
void clearActiveButton();
|
void clearActiveButton();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Re-evaluates which build buttons are enabled from the current building block
|
||||||
|
// stock (read from the simulation). If the currently selected tool can no longer
|
||||||
|
// be afforded, it exits builder mode so the button does not stay selected.
|
||||||
|
void updateAffordability();
|
||||||
|
|
||||||
void handleEvent(std::shared_ptr<const BuilderModeExitedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BuilderModeExitedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const DemolishModeChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const BuildHotkeyPressedEvent> event) override;
|
void handleEvent(std::shared_ptr<const BuildHotkeyPressedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onBuildButton(int index);
|
void onBuildButton(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Simulation* m_sim;
|
||||||
const GameConfig* m_config;
|
const GameConfig* m_config;
|
||||||
std::vector<BuildingType> m_types;
|
std::vector<BuildingType> m_types;
|
||||||
std::vector<QPushButton*> m_buttons;
|
std::vector<QPushButton*> m_buttons;
|
||||||
std::map<BuildingType, int> m_costs;
|
std::map<BuildingType, int> m_costs;
|
||||||
int m_activeIndex;
|
std::optional<std::size_t> m_activeIndex;
|
||||||
QPushButton* m_demolishButton;
|
QPushButton* m_demolishButton;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
#include "BlueprintPanel.h"
|
#include "BlueprintPanel.h"
|
||||||
#include "BuildButtonGrid.h"
|
#include "BuildButtonGrid.h"
|
||||||
#include "BuildingBlocksChangedEvent.h"
|
|
||||||
#include "BuildingSystem.h"
|
#include "BuildingSystem.h"
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "CommandRequestedEvent.h"
|
#include "CommandRequestedEvent.h"
|
||||||
@@ -53,7 +52,7 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir,
|
|||||||
sideLayout->setSpacing(1);
|
sideLayout->setSpacing(1);
|
||||||
|
|
||||||
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel);
|
m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel);
|
||||||
m_buildButtonGrid = new BuildButtonGrid(&sim->getConfig(), m_sidePanel);
|
m_buildButtonGrid = new BuildButtonGrid(sim, &sim->getConfig(), m_sidePanel);
|
||||||
m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel);
|
m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel);
|
||||||
|
|
||||||
sideLayout->addWidget(m_selectedBuildingPanel, 1);
|
sideLayout->addWidget(m_selectedBuildingPanel, 1);
|
||||||
@@ -151,11 +150,6 @@ void MainWindow::layoutPanels()
|
|||||||
m_dimOverlay->setGeometry(0, 0, totalW, totalH);
|
m_dimOverlay->setGeometry(0, 0, totalW, totalH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event)
|
|
||||||
{
|
|
||||||
m_buildButtonGrid->updateAffordability(event->blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
|
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
|
||||||
{
|
{
|
||||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "BuildingBlocksChangedEvent.h"
|
|
||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
#include "EscapeMenuRequestedEvent.h"
|
#include "EscapeMenuRequestedEvent.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
@@ -32,8 +31,7 @@ class QCloseEvent;
|
|||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class MainWindow : public QWidget,
|
class MainWindow : public QWidget,
|
||||||
public CombinedEventHandler<BuildingBlocksChangedEvent,
|
public CombinedEventHandler<SchematicChoicesAvailableEvent,
|
||||||
SchematicChoicesAvailableEvent,
|
|
||||||
GameOverEvent,
|
GameOverEvent,
|
||||||
WinEvent,
|
WinEvent,
|
||||||
EscapeMenuRequestedEvent,
|
EscapeMenuRequestedEvent,
|
||||||
@@ -52,7 +50,6 @@ protected:
|
|||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> event) override;
|
|
||||||
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
|
void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
|
void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const WinEvent> event) override;
|
void handleEvent(std::shared_ptr<const WinEvent> event) override;
|
||||||
|
|||||||
Reference in New Issue
Block a user