Compare commits
3 Commits
cdf89ce0dd
...
92ab1dab54
| Author | SHA1 | Date | |
|---|---|---|---|
| 92ab1dab54 | |||
| 94b5d941ba | |||
| 53af44db04 |
@@ -31,6 +31,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BeamFiredEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DebugDrawToggledEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CommandRequestedEvent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PlayerCommandsAppliedEvent.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
|
||||
13
src/lib/eventsystem/event/PlayerCommandsAppliedEvent.h
Normal file
13
src/lib/eventsystem/event/PlayerCommandsAppliedEvent.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Event.h"
|
||||
|
||||
// Emitted by GameWorldView once per frame after queued player commands have been
|
||||
// drained and applied to the simulation. It lets presentation widgets refresh
|
||||
// even while the game is paused (no tick advances, so no TickAdvancedEvent), for
|
||||
// example so a shipyard's layout preview appears immediately after its schematic
|
||||
// is chosen. It is a UI notification only and never feeds back into the command
|
||||
// queue, so it has no effect on replay recording or determinism.
|
||||
class PlayerCommandsAppliedEvent : public Event
|
||||
{
|
||||
};
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "ExpansionCostChangedEvent.h"
|
||||
#include "GameSpeedChangedEvent.h"
|
||||
#include "SchematicChoicesAvailableEvent.h"
|
||||
#include "PlayerCommandsAppliedEvent.h"
|
||||
#include "TickAdvancedEvent.h"
|
||||
|
||||
namespace
|
||||
@@ -212,6 +213,7 @@ void GameWorldView::onFrame()
|
||||
// Drain queued player commands once per frame, before the tick batch. This
|
||||
// runs even at 0x so a paused player sees placed construction sites
|
||||
// immediately, while staying deterministic (see docs/replay_design.md).
|
||||
const bool commandsApplied = m_commandManager.hasPending();
|
||||
m_commandManager.drain();
|
||||
|
||||
// A drained Reset reinitialized the simulation; reset the view to match.
|
||||
@@ -221,6 +223,17 @@ void GameWorldView::onFrame()
|
||||
resetForNewGame();
|
||||
}
|
||||
|
||||
// Notify presentation widgets that queued commands were applied, so a
|
||||
// paused player still sees the effect (e.g. a shipyard's layout preview
|
||||
// after picking a schematic) even though no tick advances. UI-only: this
|
||||
// does not touch the command queue or simulation, so replay recording
|
||||
// and determinism are unaffected.
|
||||
if (commandsApplied)
|
||||
{
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<PlayerCommandsAppliedEvent>());
|
||||
}
|
||||
|
||||
const int ticks = m_tickDriver.advance(
|
||||
static_cast<double>(elapsed), m_gameSpeedMultiplier);
|
||||
for (int i = 0; i < ticks; ++i)
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "ItemType.h"
|
||||
#include "LayoutDialogRequestedEvent.h"
|
||||
#include "ModulesConfig.h"
|
||||
#include "PlayerCommandsAppliedEvent.h"
|
||||
#include "RecipeSelectionDialog.h"
|
||||
#include "RecipeSelectionRequestedEvent.h"
|
||||
#include "Rotation.h"
|
||||
@@ -294,38 +295,12 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
}
|
||||
m_recipeSelectButton->show();
|
||||
|
||||
if (type == BuildingType::Shipyard && !recipeId.empty())
|
||||
{
|
||||
const ShipDef* sDef = findShipDef(recipeId);
|
||||
if (sDef && !sDef->layout.empty())
|
||||
{
|
||||
ShipLayoutConfig layout;
|
||||
if (shipLayout.has_value())
|
||||
{
|
||||
layout = *shipLayout;
|
||||
}
|
||||
m_layoutPreview->setShipAndLayout(
|
||||
sDef->layout, layout, &m_config->modules.modules);
|
||||
m_layoutPreview->show();
|
||||
m_configureLayoutBtn->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
}
|
||||
updateShipyardLayoutWidgets(type, recipeId, shipLayout);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recipeSelectButton->hide();
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
updateShipyardLayoutWidgets(type, recipeId, shipLayout);
|
||||
}
|
||||
|
||||
// Belt "Clear" removes items from a live belt tile; a construction site has
|
||||
@@ -362,6 +337,16 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
}
|
||||
|
||||
if (m_singleIsSite)
|
||||
{
|
||||
refreshSiteProgress(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
refreshBuffers(b);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::refreshSiteProgress(const ConstructionSite* s)
|
||||
{
|
||||
QString progress;
|
||||
if (s->completesAt == 0)
|
||||
@@ -390,11 +375,6 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
|
||||
}
|
||||
m_buffersLabel->setText(progress);
|
||||
}
|
||||
else
|
||||
{
|
||||
refreshBuffers(b);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
||||
{
|
||||
@@ -530,15 +510,38 @@ void SelectedBuildingPanel::refreshBuffers(const Building* b)
|
||||
|
||||
m_buffersLabel->setText(bufText);
|
||||
|
||||
if (b->type == BuildingType::Shipyard && shipDef && !shipDef->layout.empty())
|
||||
// The recipe/schematic is applied via a queued command that only drains on a
|
||||
// later frame, so the per-tick refresh must own the shipyard preview and the
|
||||
// Configure Layout button's visibility; otherwise they stay hidden until the
|
||||
// building is re-selected (which re-runs buildSingle).
|
||||
updateShipyardLayoutWidgets(b->type, b->recipeId, b->shipLayout);
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::updateShipyardLayoutWidgets(
|
||||
BuildingType type,
|
||||
const std::string& recipeId,
|
||||
const std::optional<ShipLayoutConfig>& shipLayout)
|
||||
{
|
||||
const ShipDef* shipDef = (type == BuildingType::Shipyard)
|
||||
? findShipDef(recipeId)
|
||||
: nullptr;
|
||||
|
||||
if (shipDef && !shipDef->layout.empty())
|
||||
{
|
||||
ShipLayoutConfig layout;
|
||||
if (b->shipLayout.has_value())
|
||||
if (shipLayout.has_value())
|
||||
{
|
||||
layout = *b->shipLayout;
|
||||
layout = *shipLayout;
|
||||
}
|
||||
m_layoutPreview->setShipAndLayout(
|
||||
shipDef->layout, layout, &m_config->modules.modules);
|
||||
m_layoutPreview->show();
|
||||
m_configureLayoutBtn->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layoutPreview->hide();
|
||||
m_configureLayoutBtn->hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +566,21 @@ const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
|
||||
{
|
||||
refreshSelectionDisplay(RefreshReason::PeriodicTick);
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::handleEvent(
|
||||
std::shared_ptr<const PlayerCommandsAppliedEvent> /*event*/)
|
||||
{
|
||||
// Player commands (e.g. choosing a shipyard schematic) are applied by a
|
||||
// queued drain, not synchronously. When the game is paused no tick advances,
|
||||
// so TickAdvancedEvent never fires; refresh here too, otherwise the panel
|
||||
// would not reflect the change until the next tick or a re-selection.
|
||||
refreshSelectionDisplay(RefreshReason::CommandApplied);
|
||||
}
|
||||
|
||||
void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason)
|
||||
{
|
||||
if (m_selectedEntity.has_value())
|
||||
{
|
||||
@@ -586,8 +604,19 @@ void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent>
|
||||
}
|
||||
const ConstructionSite* s = m_sim->buildings().findSite(m_singleBuildingId);
|
||||
if (s)
|
||||
{
|
||||
// A periodic tick only advances construction progress, so update just the
|
||||
// progress label. Rebuilding every tick would hide/re-show all widgets and
|
||||
// cancel any in-progress click on the recipe button. An applied command
|
||||
// may have changed the site's recipe/layout, so rebuild in that case.
|
||||
if (reason == RefreshReason::CommandApplied)
|
||||
{
|
||||
rebuild();
|
||||
}
|
||||
else
|
||||
{
|
||||
refreshSiteProgress(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
buildEmpty();
|
||||
@@ -647,9 +676,11 @@ void SelectedBuildingPanel::onSelectRecipeClicked()
|
||||
return;
|
||||
}
|
||||
// The emit is synchronous: MainWindow pauses the game, runs the modal
|
||||
// selection dialog, applies the chosen recipe/schematic, and restores the
|
||||
// speed before this returns. rebuild() then refreshes the button caption,
|
||||
// tooltip, preview, and buffers for the new selection.
|
||||
// selection dialog, and restores the speed before this returns. The chosen
|
||||
// recipe/schematic is only *enqueued* as a command, though, and drains on a
|
||||
// later frame -- so this rebuild() still sees the old recipe. The per-tick
|
||||
// refreshBuffers() path picks up the new schematic (and shows the layout
|
||||
// preview + Configure Layout button) once the command has been applied.
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<RecipeSelectionRequestedEvent>(m_singleBuildingId));
|
||||
rebuild();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "EventHandler.h"
|
||||
#include "GameConfig.h"
|
||||
#include "PlayerCommandsAppliedEvent.h"
|
||||
#include "RecipesConfig.h"
|
||||
#include "SelectionChangedEvent.h"
|
||||
#include "ShipLayout.h"
|
||||
@@ -33,6 +34,7 @@ class QVBoxLayout;
|
||||
|
||||
class SelectedBuildingPanel : public QWidget,
|
||||
public CombinedEventHandler<TickAdvancedEvent,
|
||||
PlayerCommandsAppliedEvent,
|
||||
EntitySelectedEvent,
|
||||
SelectionChangedEvent,
|
||||
DebugDrawToggledEvent>
|
||||
@@ -46,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event) override;
|
||||
@@ -56,7 +59,18 @@ private slots:
|
||||
void onSplitterFilterChanged();
|
||||
|
||||
private:
|
||||
// Why the selection display is being refreshed. A periodic tick only needs a
|
||||
// lightweight content update (e.g. a construction site's progress label),
|
||||
// whereas an applied player command may have changed the configuration and
|
||||
// needs a full structural rebuild.
|
||||
enum class RefreshReason
|
||||
{
|
||||
PeriodicTick,
|
||||
CommandApplied
|
||||
};
|
||||
|
||||
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
||||
void refreshSelectionDisplay(RefreshReason reason);
|
||||
void rebuild();
|
||||
void hideAllWidgets();
|
||||
void clearContent();
|
||||
@@ -64,6 +78,10 @@ private:
|
||||
void buildSingle(BuildingId id);
|
||||
void buildMulti(const std::vector<BuildingId>& ids);
|
||||
void refreshBuffers(const Building* b);
|
||||
void refreshSiteProgress(const ConstructionSite* s);
|
||||
void updateShipyardLayoutWidgets(BuildingType type,
|
||||
const std::string& recipeId,
|
||||
const std::optional<ShipLayoutConfig>& shipLayout);
|
||||
void buildSplitterFilters(const std::optional<BeltSystem::SplitterInfo>& info);
|
||||
const RecipeDef* findRecipe(const Building* b) const;
|
||||
const ShipDef* findShipDef(const std::string& id) const;
|
||||
|
||||
Reference in New Issue
Block a user