#include "ShipStatsPanel.h" #include #include #include #include "GameConfig.h" #include "ShipStatsCalculator.h" #include "ThreatCostCalculator.h" namespace { QString fmt(float value) { return QString::number(static_cast(value), 'f', 1); } QLabel* makeSectionHeader(const QString& text, QWidget* parent) { QLabel* label = new QLabel(text, parent); QFont f = label->font(); f.setBold(true); label->setFont(f); return label; } QLabel* makeStatLabel(QWidget* parent) { return new QLabel(parent); } } // namespace ShipStatsPanel::ShipStatsPanel(const GameConfig* config, QWidget* parent) : QWidget(parent) , m_config(config) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(4, 4, 4, 4); layout->setSpacing(2); layout->setAlignment(Qt::AlignTop); // Hull stats — always visible. m_hpLabel = makeStatLabel(this); m_speedLabel = makeStatLabel(this); m_sensorRangeLabel = makeStatLabel(this); m_mainAccelLabel = makeStatLabel(this); m_maneuveringAccelLabel = makeStatLabel(this); m_angularAccelLabel = makeStatLabel(this); m_maxRotSpeedLabel = makeStatLabel(this); m_cargoCapacityLabel = makeStatLabel(this); m_cargoCapacityLabel->setVisible(false); layout->addWidget(m_hpLabel); layout->addWidget(m_speedLabel); layout->addWidget(m_sensorRangeLabel); layout->addWidget(m_mainAccelLabel); layout->addWidget(m_maneuveringAccelLabel); layout->addWidget(m_angularAccelLabel); layout->addWidget(m_maxRotSpeedLabel); layout->addWidget(m_cargoCapacityLabel); // Weapon capability section. m_weaponSection = new QWidget(this); { QVBoxLayout* sl = new QVBoxLayout(m_weaponSection); sl->setContentsMargins(0, 4, 0, 0); sl->setSpacing(2); sl->addWidget(makeSectionHeader(tr("Weapons"), m_weaponSection)); m_weaponDpsLabel = makeStatLabel(m_weaponSection); m_weaponRangeLabel = makeStatLabel(m_weaponSection); sl->addWidget(m_weaponDpsLabel); sl->addWidget(m_weaponRangeLabel); } m_weaponSection->setVisible(false); layout->addWidget(m_weaponSection); // Salvage capability section. m_salvageSection = new QWidget(this); { QVBoxLayout* sl = new QVBoxLayout(m_salvageSection); sl->setContentsMargins(0, 4, 0, 0); sl->setSpacing(2); sl->addWidget(makeSectionHeader(tr("Salvage"), m_salvageSection)); m_salvageRateLabel = makeStatLabel(m_salvageSection); m_salvageRangeLabel = makeStatLabel(m_salvageSection); sl->addWidget(m_salvageRateLabel); sl->addWidget(m_salvageRangeLabel); } m_salvageSection->setVisible(false); layout->addWidget(m_salvageSection); // Repair capability section. m_repairSection = new QWidget(this); { QVBoxLayout* sl = new QVBoxLayout(m_repairSection); sl->setContentsMargins(0, 4, 0, 0); sl->setSpacing(2); sl->addWidget(makeSectionHeader(tr("Repair"), m_repairSection)); m_repairRateLabel = makeStatLabel(m_repairSection); m_repairRangeLabel = makeStatLabel(m_repairSection); sl->addWidget(m_repairRateLabel); sl->addWidget(m_repairRangeLabel); } m_repairSection->setVisible(false); layout->addWidget(m_repairSection); // Current behavior — live entities only; hidden in the static design // preview (REQ-UI-SHIP-BEHAVIOR). m_behaviorLabel = makeSectionHeader(QString(), this); m_behaviorLabel->setVisible(false); layout->addWidget(m_behaviorLabel); // Threat cost — debug-only, initially hidden. m_threatCostLabel = makeStatLabel(this); m_threatCostLabel->setVisible(false); layout->addWidget(m_threatCostLabel); layout->addStretch(); } void ShipStatsPanel::refresh(const std::string& shipId, const std::vector& modules) { const ShipStats stats = calculateShipStats(*m_config, shipId, modules); const QString hpText = tr("HP: %1").arg(static_cast(stats.hp + 0.5f)); applyStats(stats, hpText); const double threat = calculateShipThreatCost(m_config->threatCosts, *m_config, shipId, modules); setThreatCost(threat); // The static design preview has no live behavior to show. m_behaviorLabel->setVisible(false); } void ShipStatsPanel::refreshFromLive(const ShipStats& stats, float currentHp) { const QString hpText = tr("HP: %1 / %2") .arg(static_cast(currentHp + 0.5f)) .arg(static_cast(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( tr("Sensor Range: %1 tiles").arg(fmt(stats.sensorRange_tiles))); m_mainAccelLabel->setText( tr("Main Accel: %1 tiles/s\xc2\xb2").arg(fmt(stats.mainAcceleration_tpss))); m_maneuveringAccelLabel->setText( tr("Maneuvering Accel: %1 tiles/s\xc2\xb2").arg(fmt(stats.maneuveringAcceleration_tpss))); m_angularAccelLabel->setText( tr("Angular Accel: %1 rad/s\xc2\xb2").arg(fmt(stats.angularAcceleration_radpss))); m_maxRotSpeedLabel->setText( tr("Max Rotation: %1 rad/s").arg(fmt(stats.maxRotationSpeed_radps))); // Cargo capacity is shown only when the ship can actually hold cargo // (REQ-MOD-UI-STATS-PANEL). if (stats.cargoCapacity > 0) { m_cargoCapacityLabel->setText( tr("Cargo Capacity: %1").arg(stats.cargoCapacity)); m_cargoCapacityLabel->setVisible(true); } else { m_cargoCapacityLabel->setVisible(false); } if (stats.weapons.has_value()) { m_weaponDpsLabel->setText( tr("DPS: %1").arg(fmt(stats.weapons->combinedDps))); m_weaponRangeLabel->setText( tr("Range: %1 tiles").arg(fmt(stats.weapons->maxRange_tiles))); m_weaponSection->setVisible(true); } else { m_weaponSection->setVisible(false); } if (stats.salvage.has_value()) { m_salvageRateLabel->setText( tr("Collection Rate: %1/s").arg(fmt(stats.salvage->combinedCollectionRate))); m_salvageRangeLabel->setText( tr("Range: %1 tiles").arg(fmt(stats.salvage->maxRange_tiles))); m_salvageSection->setVisible(true); } else { m_salvageSection->setVisible(false); } if (stats.repair.has_value()) { m_repairRateLabel->setText( tr("Repair Rate: %1 HP/s").arg(fmt(stats.repair->combinedRepairRate_hps))); m_repairRangeLabel->setText( tr("Range: %1 tiles").arg(fmt(stats.repair->maxRange_tiles))); m_repairSection->setVisible(true); } else { m_repairSection->setVisible(false); } } void ShipStatsPanel::setBehavior(BehaviorKind kind) { QString label; switch (kind) { case BehaviorKind::Retreat: label = tr("Retreating"); break; case BehaviorKind::Attack: label = tr("Engaging"); break; case BehaviorKind::SalvageScrap: case BehaviorKind::DeliverScrap: label = tr("Salvaging"); break; case BehaviorKind::Repair: label = tr("Repairing"); break; case BehaviorKind::Rally: label = tr("Rallying"); break; case BehaviorKind::Standby: label = tr("Standby"); break; case BehaviorKind::Advance: label = tr("Advancing"); break; case BehaviorKind::None: break; } if (label.isEmpty()) { m_behaviorLabel->setVisible(false); return; } m_behaviorLabel->setText(tr("Behavior: %1").arg(label)); m_behaviorLabel->setVisible(true); } void ShipStatsPanel::setThreatCost(double cost) { m_threatCostLabel->setText(tr("Threat Cost: %1").arg(cost, 0, 'f', 1)); m_threatCostLabel->setVisible(m_debugDraw); } void ShipStatsPanel::setDebugDrawEnabled(bool enabled) { m_debugDraw = enabled; m_threatCostLabel->setVisible(m_debugDraw); }