add live ship stats panel
This commit is contained in:
@@ -4,12 +4,16 @@
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPoint>
|
||||
|
||||
#include "ArenaSimulation.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "EntityHitTest.h"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "EventManager.h"
|
||||
#include "FacingComponent.h"
|
||||
#include "FactionComponent.h"
|
||||
#include "HealthComponent.h"
|
||||
@@ -198,6 +202,37 @@ std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
|
||||
return m_sim->admin().get<PositionComponent>(entity).value;
|
||||
}
|
||||
|
||||
QVector2D ArenaView::widgetToWorld(QPoint widgetPt) const
|
||||
{
|
||||
const float px = tilePx();
|
||||
if (px < 0.001f) { return QVector2D(0.0f, 0.0f); }
|
||||
return QVector2D(static_cast<float>(widgetPt.x()) / px,
|
||||
static_cast<float>(widgetPt.y()) / px);
|
||||
}
|
||||
|
||||
void ArenaView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
const QVector2D worldPos = widgetToWorld(event->pos());
|
||||
entt::entity hit = entityAtWorldPos(m_sim->admin(), worldPos);
|
||||
|
||||
if (hit != entt::null)
|
||||
{
|
||||
m_selectedEntity = hit;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedEntity = std::nullopt;
|
||||
}
|
||||
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<EntitySelectedEvent>(m_selectedEntity));
|
||||
}
|
||||
|
||||
QOpenGLWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -268,7 +303,7 @@ void ArenaView::drawScrap(QPainter& painter)
|
||||
void ArenaView::drawStations(QPainter& painter)
|
||||
{
|
||||
m_sim->admin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[&](entt::entity /*e*/, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h)
|
||||
[&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h)
|
||||
{
|
||||
const BuildingType visType = f.isEnemy
|
||||
? BuildingType::EnemyDefenceStation
|
||||
@@ -304,6 +339,13 @@ void ArenaView::drawStations(QPainter& painter)
|
||||
painter.fillRect(QRectF(bboxRect.left(), barY, barW * static_cast<qreal>(fraction), barH),
|
||||
f.isEnemy ? QColor(200, 60, 60) : QColor(60, 200, 60));
|
||||
}
|
||||
|
||||
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
|
||||
{
|
||||
painter.setPen(QPen(QColor(255, 255, 0), 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(bboxRect.adjusted(-2, -2, 2, 2));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -311,7 +353,7 @@ void ArenaView::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)
|
||||
{
|
||||
@@ -349,6 +391,14 @@ void ArenaView::drawShips(QPainter& painter)
|
||||
painter.fillRect(QRectF(barX, barY, barW * static_cast<qreal>(fraction), barH),
|
||||
fac.isEnemy ? QColor(200, 60, 60) : QColor(60, 200, 60));
|
||||
}
|
||||
|
||||
if (m_selectedEntity.has_value() && *m_selectedEntity == e)
|
||||
{
|
||||
const qreal radius = static_cast<qreal>(tilePx()) * 0.55;
|
||||
painter.setPen(QPen(QColor(255, 255, 0), 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawEllipse(center, radius, radius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
@@ -11,6 +12,7 @@
|
||||
#include "FireEvent.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "Tick.h"
|
||||
#include "TickDriver.h"
|
||||
#include "VisualsConfig.h"
|
||||
@@ -37,6 +39,7 @@ signals:
|
||||
|
||||
protected:
|
||||
void paintGL() override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void onFrame();
|
||||
@@ -55,6 +58,7 @@ private:
|
||||
QRectF tileRect(QPoint tile) const;
|
||||
|
||||
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
||||
QVector2D widgetToWorld(QPoint widgetPt) const;
|
||||
|
||||
struct ActiveBeam
|
||||
{
|
||||
@@ -79,4 +83,6 @@ private:
|
||||
|
||||
std::vector<ActiveBeam> m_activeBeams;
|
||||
bool m_finishedEmitted;
|
||||
|
||||
std::optional<entt::entity> m_selectedEntity;
|
||||
};
|
||||
|
||||
@@ -209,7 +209,7 @@ void BalancingWindow::inspectArena(int index)
|
||||
entry.widget->updateStatus(m_inspectedSim->status());
|
||||
|
||||
m_inspectWindow = new InspectWindow(
|
||||
m_inspectedSim.get(), &m_visuals, entry.config.name, nullptr);
|
||||
m_inspectedSim.get(), &m_gameConfig, &m_visuals, entry.config.name, nullptr);
|
||||
connect(m_inspectWindow, &InspectWindow::closed,
|
||||
this, &BalancingWindow::closeInspectWindow);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BalancingConfig.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BalancingWindow.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InspectWindow.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ui/ShipStatsPanel.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsConfig.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsLoader.h
|
||||
PARENT_SCOPE
|
||||
@@ -20,6 +21,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BalancingConfig.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BalancingWindow.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InspectWindow.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ui/ShipStatsPanel.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../ui/VisualsLoader.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -9,14 +9,24 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "ArenaView.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "HealthComponent.h"
|
||||
#include "ModuleOwnerComponent.h"
|
||||
#include "ShipIdentityComponent.h"
|
||||
#include "ShipStatsCalculator.h"
|
||||
#include "ShipStatsPanel.h"
|
||||
#include "StationBodyComponent.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
const double InspectWindow::kSpeeds[] = { 0.0, 0.5, 1.0, 2.0, 10.0 };
|
||||
const int InspectWindow::kSpeedCount = 5;
|
||||
|
||||
InspectWindow::InspectWindow(ArenaSimulation* sim, const VisualsConfig* visuals,
|
||||
InspectWindow::InspectWindow(ArenaSimulation* sim, const GameConfig* config,
|
||||
const VisualsConfig* visuals,
|
||||
const std::string& arenaName, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_sim(sim)
|
||||
, m_config(config)
|
||||
{
|
||||
setWindowTitle(tr("Inspect \u2014 %1").arg(QString::fromStdString(arenaName)));
|
||||
resize(900, 700);
|
||||
@@ -96,6 +106,27 @@ InspectWindow::InspectWindow(ArenaSimulation* sim, const VisualsConfig* visuals,
|
||||
team2Layout->addStretch();
|
||||
infoLayout->addLayout(team2Layout);
|
||||
|
||||
// Entity stats section (right side of info panel)
|
||||
QVBoxLayout* entityLayout = new QVBoxLayout();
|
||||
m_entityTitleLabel = new QLabel(infoPanel);
|
||||
QFont entityTitleFont = m_entityTitleLabel->font();
|
||||
entityTitleFont.setBold(true);
|
||||
m_entityTitleLabel->setFont(entityTitleFont);
|
||||
m_entityTitleLabel->hide();
|
||||
entityLayout->addWidget(m_entityTitleLabel);
|
||||
|
||||
m_entityStatsPanel = new ShipStatsPanel(config, infoPanel);
|
||||
m_entityStatsPanel->hide();
|
||||
entityLayout->addWidget(m_entityStatsPanel);
|
||||
|
||||
m_stationStatsLabel = new QLabel(infoPanel);
|
||||
m_stationStatsLabel->setWordWrap(true);
|
||||
m_stationStatsLabel->hide();
|
||||
entityLayout->addWidget(m_stationStatsLabel);
|
||||
|
||||
entityLayout->addStretch();
|
||||
infoLayout->addLayout(entityLayout);
|
||||
|
||||
mainLayout->addWidget(infoPanel);
|
||||
}
|
||||
|
||||
@@ -108,6 +139,13 @@ InspectWindow::InspectWindow(ArenaSimulation* sim, const VisualsConfig* visuals,
|
||||
pollStatus();
|
||||
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
registerForEvent();
|
||||
}
|
||||
|
||||
InspectWindow::~InspectWindow()
|
||||
{
|
||||
unregisterForEvent();
|
||||
}
|
||||
|
||||
void InspectWindow::closeEvent(QCloseEvent* event)
|
||||
@@ -151,6 +189,7 @@ void InspectWindow::pollStatus()
|
||||
{
|
||||
const ArenaStatus status = m_sim->status();
|
||||
updateInfoPanel(status);
|
||||
refreshEntityStats();
|
||||
}
|
||||
|
||||
void InspectWindow::updateInfoPanel(const ArenaStatus& status)
|
||||
@@ -186,3 +225,140 @@ void InspectWindow::updateInfoPanel(const ArenaStatus& status)
|
||||
content->setText(lines);
|
||||
}
|
||||
}
|
||||
|
||||
void InspectWindow::handleEvent(std::shared_ptr<const EntitySelectedEvent> event)
|
||||
{
|
||||
if (event->entity.has_value())
|
||||
{
|
||||
m_selectedEntity = event->entity;
|
||||
|
||||
EntityAdmin& admin = m_sim->admin();
|
||||
entt::entity entity = *m_selectedEntity;
|
||||
|
||||
if (!admin.isValid(entity))
|
||||
{
|
||||
m_selectedEntity = std::nullopt;
|
||||
m_entityTitleLabel->hide();
|
||||
m_entityStatsPanel->hide();
|
||||
m_stationStatsLabel->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (admin.hasAll<ShipIdentityComponent>(entity))
|
||||
{
|
||||
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();
|
||||
}
|
||||
else if (admin.hasAll<StationBodyComponent>(entity))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_selectedEntity = std::nullopt;
|
||||
m_entityTitleLabel->hide();
|
||||
m_entityStatsPanel->hide();
|
||||
m_stationStatsLabel->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectWindow::refreshEntityStats()
|
||||
{
|
||||
if (!m_selectedEntity.has_value()) { return; }
|
||||
|
||||
EntityAdmin& admin = m_sim->admin();
|
||||
entt::entity entity = *m_selectedEntity;
|
||||
|
||||
if (!admin.isValid(entity))
|
||||
{
|
||||
m_selectedEntity = std::nullopt;
|
||||
m_entityTitleLabel->hide();
|
||||
m_entityStatsPanel->hide();
|
||||
m_stationStatsLabel->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const HealthComponent& health = admin.get<HealthComponent>(entity);
|
||||
if (health.hp <= 0.0f)
|
||||
{
|
||||
m_selectedEntity = std::nullopt;
|
||||
m_entityTitleLabel->hide();
|
||||
m_entityStatsPanel->hide();
|
||||
m_stationStatsLabel->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (admin.hasAll<ShipIdentityComponent>(entity))
|
||||
{
|
||||
const ShipStats stats = buildShipStatsFromEntity(admin, entity);
|
||||
m_entityStatsPanel->refreshFromLive(stats, health.hp);
|
||||
}
|
||||
else if (admin.hasAll<StationBodyComponent>(entity))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -8,18 +9,27 @@
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
|
||||
#include "ArenaSimulation.h"
|
||||
#include "EntitySelectedEvent.h"
|
||||
#include "EventHandler.h"
|
||||
#include "GameConfig.h"
|
||||
#include "VisualsConfig.h"
|
||||
|
||||
class ArenaView;
|
||||
class ShipStatsPanel;
|
||||
|
||||
class InspectWindow : public QWidget
|
||||
class InspectWindow : public QWidget,
|
||||
public EventHandler<EntitySelectedEvent>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InspectWindow(ArenaSimulation* sim, const VisualsConfig* visuals,
|
||||
InspectWindow(ArenaSimulation* sim, const GameConfig* config,
|
||||
const VisualsConfig* visuals,
|
||||
const std::string& arenaName, QWidget* parent = nullptr);
|
||||
~InspectWindow() override;
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
@@ -28,6 +38,9 @@ protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
|
||||
|
||||
private slots:
|
||||
void onSpeedButton(int index);
|
||||
void onSpeedChanged(double multiplier);
|
||||
@@ -35,9 +48,11 @@ private slots:
|
||||
|
||||
private:
|
||||
void updateInfoPanel(const ArenaStatus& status);
|
||||
void refreshEntityStats();
|
||||
|
||||
ArenaSimulation* m_sim;
|
||||
ArenaView* m_arenaView;
|
||||
ArenaSimulation* m_sim;
|
||||
const GameConfig* m_config;
|
||||
ArenaView* m_arenaView;
|
||||
|
||||
std::vector<QPushButton*> m_speedButtons;
|
||||
QLabel* m_team1Header;
|
||||
@@ -46,6 +61,11 @@ private:
|
||||
QLabel* m_team2Content;
|
||||
QTimer* m_pollTimer;
|
||||
|
||||
std::optional<entt::entity> m_selectedEntity;
|
||||
QLabel* m_entityTitleLabel;
|
||||
ShipStatsPanel* m_entityStatsPanel;
|
||||
QLabel* m_stationStatsLabel;
|
||||
|
||||
static const double kSpeeds[];
|
||||
static const int kSpeedCount;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user