add live ship stats panel
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
#include "BeltSystem.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "EntityHitTest.h"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "EventManager.h"
|
||||
#include "FacingComponent.h"
|
||||
#include "FactionComponent.h"
|
||||
@@ -338,6 +340,13 @@ QPoint GameWorldView::widgetToTile(QPoint widgetPt) const
|
||||
return QPoint(static_cast<int>(std::floor(wx)), static_cast<int>(std::floor(wy)));
|
||||
}
|
||||
|
||||
QVector2D GameWorldView::widgetToWorld(QPoint widgetPt) const
|
||||
{
|
||||
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + m_scrollXTiles;
|
||||
const float wy = static_cast<float>(widgetPt.y()) / tilePx();
|
||||
return QVector2D(wx, wy);
|
||||
}
|
||||
|
||||
QRectF GameWorldView::tileRect(QPoint tile) const
|
||||
{
|
||||
const QPointF tl = tileToWidget(tile);
|
||||
@@ -834,7 +843,7 @@ void GameWorldView::drawScrap(QPainter& painter)
|
||||
void GameWorldView::drawStations(QPainter& painter)
|
||||
{
|
||||
m_sim->admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f,
|
||||
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f,
|
||||
const HealthComponent& h)
|
||||
{
|
||||
const BuildingType visType = f.isEnemy
|
||||
@@ -860,6 +869,13 @@ void GameWorldView::drawStations(QPainter& painter)
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(bboxRect);
|
||||
|
||||
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
|
||||
{
|
||||
painter.setPen(QPen(m_visuals->overlays.selectedOutline, 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(bboxRect.adjusted(-2, -2, 2, 2));
|
||||
}
|
||||
|
||||
// HP bar below footprint.
|
||||
if (h.maxHp > 0.0f)
|
||||
{
|
||||
@@ -879,7 +895,7 @@ void GameWorldView::drawShips(QPainter& painter)
|
||||
{
|
||||
m_sim->admin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
|
||||
FactionComponent, HealthComponent>(
|
||||
[&](entt::entity /*e*/, const ShipIdentityComponent& si,
|
||||
[&](entt::entity e, const ShipIdentityComponent& si,
|
||||
const PositionComponent& pos, const FacingComponent& facing,
|
||||
const FactionComponent& fac, const HealthComponent& h)
|
||||
{
|
||||
@@ -906,6 +922,14 @@ void GameWorldView::drawShips(QPainter& painter)
|
||||
painter.setBrush(it->second.fill);
|
||||
painter.drawPolygon(tri);
|
||||
|
||||
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
|
||||
{
|
||||
painter.setPen(QPen(m_visuals->overlays.selectedOutline, 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
const qreal r = static_cast<qreal>(fwd) + 2.0;
|
||||
painter.drawEllipse(center, r, r);
|
||||
}
|
||||
|
||||
if (h.maxHp > 0.0f)
|
||||
{
|
||||
const float fraction = std::max(0.0f, h.hp / h.maxHp);
|
||||
@@ -1271,41 +1295,62 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildingId id = buildingAtTile(tile);
|
||||
if (id == kInvalidBuildingId)
|
||||
const QVector2D worldPos = widgetToWorld(event->pos());
|
||||
const entt::entity hitEntity = entityAtWorldPos(m_sim->admin(), worldPos);
|
||||
|
||||
if (hitEntity != entt::null)
|
||||
{
|
||||
id = siteAtTile(tile);
|
||||
}
|
||||
if (id != kInvalidBuildingId)
|
||||
{
|
||||
if (event->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
bool found = false;
|
||||
std::vector<BuildingId> newSel;
|
||||
for (BuildingId sel : m_selectedBuildingIds)
|
||||
{
|
||||
if (sel == id) { found = true; }
|
||||
else { newSel.push_back(sel); }
|
||||
}
|
||||
if (!found) { newSel.push_back(id); }
|
||||
m_selectedBuildingIds = newSel;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedBuildingIds = { id };
|
||||
}
|
||||
m_selectedBuildingIds.clear();
|
||||
emit selectionChanged(m_selectedBuildingIds);
|
||||
m_selectedEntity = hitEntity;
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<EntitySelectedEvent>(hitEntity));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(event->modifiers() & Qt::ControlModifier))
|
||||
if (m_selectedEntity.has_value())
|
||||
{
|
||||
m_selectedBuildingIds.clear();
|
||||
m_selectedEntity = std::nullopt;
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<EntitySelectedEvent>(std::nullopt));
|
||||
}
|
||||
|
||||
BuildingId id = buildingAtTile(tile);
|
||||
if (id == kInvalidBuildingId)
|
||||
{
|
||||
id = siteAtTile(tile);
|
||||
}
|
||||
if (id != kInvalidBuildingId)
|
||||
{
|
||||
if (event->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
bool found = false;
|
||||
std::vector<BuildingId> newSel;
|
||||
for (BuildingId sel : m_selectedBuildingIds)
|
||||
{
|
||||
if (sel == id) { found = true; }
|
||||
else { newSel.push_back(sel); }
|
||||
}
|
||||
if (!found) { newSel.push_back(id); }
|
||||
m_selectedBuildingIds = newSel;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedBuildingIds = { id };
|
||||
}
|
||||
emit selectionChanged(m_selectedBuildingIds);
|
||||
}
|
||||
m_boxSelecting = true;
|
||||
m_boxStartTile = tile;
|
||||
m_boxCurrentTile = tile;
|
||||
else
|
||||
{
|
||||
if (!(event->modifiers() & Qt::ControlModifier))
|
||||
{
|
||||
m_selectedBuildingIds.clear();
|
||||
emit selectionChanged(m_selectedBuildingIds);
|
||||
}
|
||||
m_boxSelecting = true;
|
||||
m_boxStartTile = tile;
|
||||
m_boxCurrentTile = tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "FireEvent.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Rotation.h"
|
||||
#include "Tick.h"
|
||||
@@ -106,6 +107,7 @@ private:
|
||||
const BuildingDef* findBuildingDef(BuildingType type) const;
|
||||
BuildingId buildingAtTile(QPoint tile) const;
|
||||
BuildingId siteAtTile(QPoint tile) const;
|
||||
QVector2D widgetToWorld(QPoint widgetPt) const;
|
||||
|
||||
void drawPortGlyph(QPainter& painter, QPoint bodyTile,
|
||||
Rotation direction, const QColor& color);
|
||||
@@ -166,6 +168,7 @@ private:
|
||||
bool m_debugDraw;
|
||||
|
||||
std::vector<BuildingId> m_selectedBuildingIds;
|
||||
std::optional<entt::entity> m_selectedEntity;
|
||||
bool m_boxSelecting;
|
||||
QPoint m_boxStartTile;
|
||||
QPoint m_boxCurrentTile;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QWidget>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
#include "Building.h"
|
||||
#include "BuildingId.h"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "EventHandler.h"
|
||||
#include "GameConfig.h"
|
||||
#include "RecipesConfig.h"
|
||||
@@ -18,6 +22,7 @@
|
||||
|
||||
class Simulation;
|
||||
class ShipLayoutPreview;
|
||||
class ShipStatsPanel;
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class QListWidget;
|
||||
@@ -25,7 +30,7 @@ class QPushButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
class SelectedBuildingPanel : public QWidget,
|
||||
public EventHandler<TickAdvancedEvent>
|
||||
public CombinedEventHandler<TickAdvancedEvent, EntitySelectedEvent>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -42,6 +47,7 @@ public slots:
|
||||
|
||||
private:
|
||||
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
|
||||
|
||||
private slots:
|
||||
void onRecipeChanged(int comboIndex);
|
||||
@@ -80,4 +86,14 @@ private:
|
||||
BuildingId m_singleBuildingId;
|
||||
QPoint m_splitterTile;
|
||||
std::string m_currentRecipeId;
|
||||
|
||||
std::optional<entt::entity> m_selectedEntity;
|
||||
ShipStatsPanel* m_entityStatsPanel;
|
||||
QLabel* m_entityTitleLabel;
|
||||
QLabel* m_stationStatsLabel;
|
||||
|
||||
void buildEntityShip(entt::entity entity);
|
||||
void buildEntityStation(entt::entity entity);
|
||||
void refreshEntityStats();
|
||||
void clearEntityDisplay();
|
||||
};
|
||||
|
||||
@@ -111,9 +111,21 @@ void ShipStatsPanel::refresh(const std::string& shipId,
|
||||
const std::vector<PlacedModule>& modules)
|
||||
{
|
||||
const ShipStats stats = calculateShipStats(*m_config, shipId, level, modules);
|
||||
const QString hpText = tr("HP: %1").arg(static_cast<int>(stats.hp + 0.5f));
|
||||
applyStats(stats, hpText);
|
||||
}
|
||||
|
||||
m_hpLabel->setText(
|
||||
tr("HP: %1").arg(static_cast<int>(stats.hp + 0.5f)));
|
||||
void ShipStatsPanel::refreshFromLive(const ShipStats& stats, float currentHp)
|
||||
{
|
||||
const QString hpText = tr("HP: %1 / %2")
|
||||
.arg(static_cast<int>(currentHp + 0.5f))
|
||||
.arg(static_cast<int>(stats.hp + 0.5f));
|
||||
applyStats(stats, hpText);
|
||||
}
|
||||
|
||||
void ShipStatsPanel::applyStats(const ShipStats& stats, const QString& hpText)
|
||||
{
|
||||
m_hpLabel->setText(hpText);
|
||||
m_speedLabel->setText(
|
||||
tr("Max Speed: %1 tiles/s").arg(fmt(stats.maxSpeed_tps)));
|
||||
m_sensorRangeLabel->setText(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "ShipLayout.h"
|
||||
#include "ShipStatsCalculator.h"
|
||||
|
||||
struct GameConfig;
|
||||
class QLabel;
|
||||
@@ -21,7 +22,11 @@ public:
|
||||
int level,
|
||||
const std::vector<PlacedModule>& modules);
|
||||
|
||||
void refreshFromLive(const ShipStats& stats, float currentHp);
|
||||
|
||||
private:
|
||||
void applyStats(const ShipStats& stats, const QString& hpText);
|
||||
|
||||
const GameConfig* m_config;
|
||||
|
||||
QLabel* m_hpLabel;
|
||||
|
||||
Reference in New Issue
Block a user