Files
dota_factory/src/ui/HeaderBar.cpp
Malte Langkabel 099f0b55fc make HeaderBar read the tick, artifacts and boss wave from the simulation
The last three payloads HeaderBar still consumed as truth. All are backed by
Simulation getters (getCurrentTick, getArtifactCount, getBossWaveCounter,
getBossCountdownTicks) and the win count by world.artifacts.artifactWinCount,
so the handlers now re-read rather than trust the event.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-03 21:54:02 +02:00

228 lines
7.2 KiB
C++

#include "HeaderBar.h"
#include <cmath>
#include <string>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QPalette>
#include <QPushButton>
#include <QSignalMapper>
#include <QSize>
#include "Command.h"
#include "CommandRequestedEvent.h"
#include "EventManager.h"
#include "IconCaption.h"
#include "ItemIconCache.h"
#include "Simulation.h"
#include "SpeedChangeRequestedEvent.h"
#include "Tick.h"
namespace
{
// Item id of the building blocks resource, whose icon stands in for the "Blocks"
// word in the header stock and expand button (REQ-UI-BLOCKS-ICON).
const char* const kBlockItemId = "building_block";
}
const double HeaderBar::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
const int HeaderBar::kSpeedCount = 5;
HeaderBar::HeaderBar(const Simulation* sim, const GameConfig* config,
ItemIconCache* itemIcons, QWidget* parent)
: QWidget(parent)
, m_itemIcons(itemIcons)
, m_sim(sim)
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(8, 4, 8, 4);
layout->setSpacing(8);
m_timeLabel = new QLabel("00:00", this);
m_blocksLabel = new QLabel(this);
if (config->world.buildingBlocksTooltip)
{
m_blocksLabel->setToolTip(
QString::fromStdString(*config->world.buildingBlocksTooltip));
}
updateBlocksLabel();
m_artifactsLabel = new QLabel(tr("Artifacts: 0/?"), this);
if (config->world.artifactTooltip)
{
m_artifactsLabel->setToolTip(
QString::fromStdString(*config->world.artifactTooltip));
}
m_bossWaveLabel = new QLabel(tr("Boss Wave #1"), this);
m_nextBossLabel = new QLabel(tr("Next boss: 5:00"), this);
// Asteroid expansion button, to the left of the speed buttons (REQ-UI-HEADER,
// REQ-UI-EXPAND-BUTTON). Caption/enabled state are set on the first
// ExpansionCostChangedEvent; clicking requests an ExpandAsteroidCommand.
m_expandButton = new QPushButton(tr("Expand"), this);
connect(m_expandButton, &QPushButton::clicked, this, []() {
EventManager::getInstance()->sendEventImmediately(
std::make_shared<CommandRequestedEvent>(
std::make_shared<ExpandAsteroidCommand>()));
});
const int itemSpacing = 20;
layout->addWidget(m_timeLabel);
layout->addSpacing(itemSpacing);
layout->addWidget(m_blocksLabel);
layout->addSpacing(itemSpacing);
layout->addWidget(m_artifactsLabel);
layout->addStretch();
layout->addWidget(m_bossWaveLabel);
layout->addSpacing(itemSpacing);
layout->addWidget(m_nextBossLabel);
layout->addSpacing(itemSpacing);
layout->addWidget(m_expandButton);
layout->addSpacing(itemSpacing);
const char* labels[] = { "0x", "0.5x", "1x", "2x", "10x" };
QSignalMapper* mapper = new QSignalMapper(this);
for (int i = 0; i < kSpeedCount; ++i)
{
QPushButton* btn = new QPushButton(labels[i], this);
btn->setCheckable(true);
btn->setChecked(i == 2);
layout->addWidget(btn);
m_speedButtons.push_back(btn);
mapper->setMapping(btn, i);
connect(btn, &QPushButton::clicked, mapper, qOverload<>(&QSignalMapper::map));
}
connect(mapper, qOverload<int>(&QSignalMapper::mapped), this, &HeaderBar::onSpeedButton);
setFixedHeight(sizeHint().height());
registerForEvents();
}
HeaderBar::~HeaderBar()
{
unregisterForEvents();
}
void HeaderBar::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
const int totalSeconds = static_cast<int>(ticksToSeconds(m_sim->getCurrentTick()));
m_timeLabel->setText(
QString("%1:%2")
.arg(totalSeconds / 60, 2, 10, QChar('0'))
.arg(totalSeconds % 60, 2, 10, QChar('0')));
}
void HeaderBar::handleEvent(std::shared_ptr<const BuildingBlocksChangedEvent> /*event*/)
{
updateBlocksLabel();
updateExpandButton();
}
void HeaderBar::handleEvent(std::shared_ptr<const ExpansionCostChangedEvent> /*event*/)
{
updateExpandButton();
}
QPixmap HeaderBar::blockIcon() const
{
if (!m_itemIcons->hasIcon(kBlockItemId)) { return QPixmap(); }
// Sized to the header text height so it sits inline with the caption.
const int sizePx = QFontMetrics(font()).height();
return m_itemIcons->getPixmap(kBlockItemId, sizePx);
}
void HeaderBar::updateBlocksLabel()
{
const int blocks = m_sim->getBuildingBlocksStock();
const QPixmap icon = blockIcon();
if (icon.isNull())
{
// Fallback text form when no building_block icon exists (REQ-UI-BLOCKS-ICON).
m_blocksLabel->setText(tr("Stock: %1 Blocks").arg(blocks));
return;
}
m_blocksLabel->setPixmap(renderCaptionWithIcon(
tr("Stock: %1").arg(blocks), icon, font(),
m_blocksLabel->palette().color(QPalette::WindowText)));
}
void HeaderBar::updateExpandButton()
{
const int blocks = m_sim->getBuildingBlocksStock();
const int expansionCost = m_sim->getCurrentExpansionCost();
m_expandButton->setEnabled(blocks >= expansionCost);
const QPixmap icon = blockIcon();
if (icon.isNull())
{
// Fallback text form when no building_block icon exists (REQ-UI-EXPAND-BUTTON).
m_expandButton->setIcon(QIcon());
m_expandButton->setText(tr("Expand: %1 Blocks").arg(expansionCost));
return;
}
const QString text = tr("Expand: %1").arg(expansionCost);
const QPalette& pal = m_expandButton->palette();
const QPixmap normal = renderCaptionWithIcon(
text, icon, m_expandButton->font(), pal.color(QPalette::ButtonText));
const QPixmap greyed = renderCaptionWithIcon(
text, icon, m_expandButton->font(),
pal.color(QPalette::Disabled, QPalette::ButtonText));
QIcon buttonIcon;
buttonIcon.addPixmap(normal, QIcon::Normal);
buttonIcon.addPixmap(greyed, QIcon::Disabled);
m_expandButton->setText(QString());
m_expandButton->setIcon(buttonIcon);
const qreal dpr = normal.devicePixelRatio();
m_expandButton->setIconSize(QSize(
static_cast<int>(normal.width() / dpr),
static_cast<int>(normal.height() / dpr)));
}
void HeaderBar::handleEvent(std::shared_ptr<const GameSpeedChangedEvent> event)
{
for (int i = 0; i < kSpeedCount; ++i)
{
m_speedButtons[static_cast<std::size_t>(i)]->setChecked(
std::abs(kSpeeds[i] - event->speed) < 0.001);
}
}
void HeaderBar::handleEvent(std::shared_ptr<const BossWaveUpdatedEvent> /*event*/)
{
const Tick countdownTicks = m_sim->getBossCountdownTicks();
const int bossSeconds = static_cast<int>(
ticksToSeconds(countdownTicks > 0 ? countdownTicks : 0));
m_bossWaveLabel->setText(
tr("Boss Wave #%1")
.arg(m_sim->getBossWaveCounter()));
m_nextBossLabel->setText(
tr("Next boss: %1:%2")
.arg(bossSeconds / 60)
.arg(bossSeconds % 60, 2, 10, QChar('0')));
}
void HeaderBar::handleEvent(std::shared_ptr<const ArtifactCountChangedEvent> /*event*/)
{
m_artifactsLabel->setText(
tr("Artifacts: %1/%2")
.arg(m_sim->getArtifactCount())
.arg(m_sim->getConfig().world.artifacts.artifactWinCount));
}
void HeaderBar::onSpeedButton(int index)
{
if (index >= 0 && index < kSpeedCount)
{
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SpeedChangeRequestedEvent>(kSpeeds[index]));
}
}