add live ship stats panel

This commit is contained in:
2026-06-07 21:07:19 +02:00
parent 37a70ea321
commit f097e9a25f
20 changed files with 723 additions and 45 deletions

View File

@@ -13,6 +13,16 @@
#include <QVBoxLayout>
#include "BeltSystem.h"
#include "DynamicBodyComponent.h"
#include "EntityAdmin.h"
#include "EntitySelectedEvent.h"
#include "FactionComponent.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "ShipIdentityComponent.h"
#include "ShipStatsCalculator.h"
#include "ShipStatsPanel.h"
#include "StationBodyComponent.h"
#include "TickAdvancedEvent.h"
#include "Building.h"
#include "BuildingSystem.h"
@@ -22,6 +32,7 @@
#include "Rotation.h"
#include "ShipLayoutPreview.h"
#include "Simulation.h"
#include "WeaponComponent.h"
namespace
{
@@ -136,19 +147,39 @@ SelectedBuildingPanel::SelectedBuildingPanel(Simulation* sim,
connect(m_filterBList, &QListWidget::itemChanged,
this, &SelectedBuildingPanel::onSplitterFilterChanged);
m_entityTitleLabel = new QLabel(this);
QFont titleFont = m_entityTitleLabel->font();
titleFont.setBold(true);
m_entityTitleLabel->setFont(titleFont);
m_layout->addWidget(m_entityTitleLabel);
m_entityTitleLabel->hide();
m_entityStatsPanel = new ShipStatsPanel(config, this);
m_layout->addWidget(m_entityStatsPanel);
m_entityStatsPanel->hide();
m_stationStatsLabel = new QLabel(this);
m_stationStatsLabel->setWordWrap(true);
m_layout->addWidget(m_stationStatsLabel);
m_stationStatsLabel->hide();
buildEmpty();
registerForEvent();
registerForEvents();
}
SelectedBuildingPanel::~SelectedBuildingPanel()
{
unregisterForEvent();
unregisterForEvents();
}
void SelectedBuildingPanel::onSelectionChanged(const std::vector<BuildingId>& ids)
{
m_selectedBuildingIds = ids;
if (!ids.empty())
{
clearEntityDisplay();
}
rebuild();
}
@@ -168,7 +199,7 @@ void SelectedBuildingPanel::rebuild()
}
}
void SelectedBuildingPanel::buildEmpty()
void SelectedBuildingPanel::clearContent()
{
m_singleBuildingId = kInvalidBuildingId;
m_titleLabel->hide();
@@ -183,6 +214,14 @@ void SelectedBuildingPanel::buildEmpty()
m_buffersLabel->hide();
}
void SelectedBuildingPanel::buildEmpty()
{
clearContent();
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
}
void SelectedBuildingPanel::buildSingle(BuildingId id)
{
m_singleBuildingId = id;
@@ -498,12 +537,16 @@ const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
if (m_selectedEntity.has_value())
{
refreshEntityStats();
return;
}
if (m_singleBuildingId == kInvalidBuildingId) { return; }
const Building* b = m_sim->buildings().findBuilding(m_singleBuildingId);
if (b)
{
// If the panel was last showing this id as a construction site, the
// full building UI (recipe combo, ports, etc.) hasn't been built yet.
if (m_titleLabel->text().startsWith(tr("(Building) ")))
{
rebuild();
@@ -688,3 +731,129 @@ void SelectedBuildingPanel::onClearBelt()
m_sim->belts().clearTiles(tiles);
}
}
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const EntitySelectedEvent> event)
{
if (event->entity.has_value())
{
m_selectedEntity = event->entity;
m_selectedBuildingIds.clear();
clearContent();
EntityAdmin& admin = m_sim->admin();
entt::entity entity = *m_selectedEntity;
if (!admin.isValid(entity))
{
clearEntityDisplay();
return;
}
if (admin.hasAll<ShipIdentityComponent>(entity))
{
buildEntityShip(entity);
}
else if (admin.hasAll<StationBodyComponent>(entity))
{
buildEntityStation(entity);
}
}
else
{
clearEntityDisplay();
}
}
void SelectedBuildingPanel::buildEntityShip(entt::entity entity)
{
EntityAdmin& admin = m_sim->admin();
const ShipIdentityComponent& identity = admin.get<ShipIdentityComponent>(entity);
const HealthComponent& health = admin.get<HealthComponent>(entity);
m_entityTitleLabel->setText(tr("Ship: %1 (Lv %2)")
.arg(QString::fromStdString(identity.schematicId))
.arg(identity.level));
m_entityTitleLabel->show();
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
m_entityStatsPanel->refreshFromLive(stats, health.hp);
m_entityStatsPanel->show();
m_stationStatsLabel->hide();
}
void SelectedBuildingPanel::buildEntityStation(entt::entity entity)
{
EntityAdmin& admin = m_sim->admin();
const HealthComponent& health = admin.get<HealthComponent>(entity);
m_entityTitleLabel->setText(tr("Defence Station"));
m_entityTitleLabel->show();
float totalDps = 0.0f;
float maxRange = 0.0f;
bool hasWeapons = false;
admin.forEach<ModuleOwnerComponent, WeaponComponent>(
[&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
{
if (owner.owner != entity) { return; }
hasWeapons = true;
totalDps += w.damage * w.fireRateHz;
if (w.range_tiles > maxRange) { maxRange = w.range_tiles; }
});
QString statsText = tr("HP: %1 / %2")
.arg(static_cast<int>(health.hp + 0.5f))
.arg(static_cast<int>(health.maxHp + 0.5f));
if (hasWeapons)
{
statsText += tr("\nDPS: %1").arg(QString::number(static_cast<double>(totalDps), 'f', 1));
statsText += tr("\nRange: %1 tiles").arg(QString::number(static_cast<double>(maxRange), 'f', 1));
}
m_stationStatsLabel->setText(statsText);
m_stationStatsLabel->show();
m_entityStatsPanel->hide();
}
void SelectedBuildingPanel::refreshEntityStats()
{
if (!m_selectedEntity.has_value()) { return; }
EntityAdmin& admin = m_sim->admin();
entt::entity entity = *m_selectedEntity;
if (!admin.isValid(entity))
{
clearEntityDisplay();
return;
}
const HealthComponent& health = admin.get<HealthComponent>(entity);
if (health.hp <= 0.0f)
{
clearEntityDisplay();
return;
}
if (admin.hasAll<ShipIdentityComponent>(entity))
{
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
m_entityStatsPanel->refreshFromLive(stats, health.hp);
}
else if (admin.hasAll<StationBodyComponent>(entity))
{
buildEntityStation(entity);
}
}
void SelectedBuildingPanel::clearEntityDisplay()
{
m_selectedEntity = std::nullopt;
m_entityTitleLabel->hide();
m_entityStatsPanel->hide();
m_stationStatsLabel->hide();
}