Files
dota_factory/src/lib/sim/EntityHitTest.cpp
Malte Langkabel 51e45e10a1 Rename scrap-drop entity to "debris" in code and config
Implements the requirements rename in the codebase. The salvageable object
dropped by destroyed ships and defence stations is now the "debris" entity;
the scrap resource it yields (cargo, delivery, smelting, threat,
scrap_drop_formula, scrap_per_threat) is unchanged.

Entity renames: ScrapDataComponent->DebrisComponent, ScrapSystem->DebrisSystem,
ScrapInfo->DebrisInfo, ScrapSelectionChangedEvent->DebrisSelectionChangedEvent
(member scrap->debris), spawnScrap->spawnDebris, getScraps->getDebrisSystem,
getAllScrapInfo->getAllDebrisInfo, scrapAtWorldPos/scrapInBox->debris*,
SalvageScrapBehavior::scrapTarget->debrisTarget, PendingCollection::scrap->debris,
ScrapTest.cpp->DebrisTest.cpp. Config key scrap_despawn_seconds->
debris_despawn_seconds (WorldConfig.scrapDespawnSeconds->debrisDespawnSeconds).
The salvage/deliver behavior classes keep their names (they act on the scrap
resource).

Also implements the new REQ-UI-DEBRIS-PANEL behavior in SelectedBuildingPanel:
a single selected piece of debris shows a "Debris" heading plus a "Scrap" stat
row; a multi/mixed field selection appends "Debris x N" and "Scrap x N" lines.

Updates the four renamed REQ-ID references in comments/docs, plus architecture.md
and derived.md. All 449 test cases pass; app, tests, and balancing targets build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
2026-07-23 13:10:04 +02:00

148 lines
5.1 KiB
C++

#include "EntityHitTest.h"
#include <algorithm>
#include <cmath>
#include "EntityAdmin.h"
#include "PositionComponent.h"
#include "DebrisComponent.h"
#include "ShipIdentityComponent.h"
#include "StationBodyComponent.h"
#include "HealthComponent.h"
entt::entity entityAtWorldPos(EntityAdmin& admin, QVector2D worldPos)
{
const QPoint tile(static_cast<int>(std::floor(worldPos.x())),
static_cast<int>(std::floor(worldPos.y())));
entt::entity stationHit = entt::null;
admin.forEach<StationBodyComponent, HealthComponent>(
[&](entt::entity entity, const StationBodyComponent& sb, const HealthComponent& h)
{
if (stationHit != entt::null) { return; }
if (h.hp <= 0.0f) { return; }
for (const QPoint& cell : sb.bodyCells)
{
if (cell == tile)
{
stationHit = entity;
return;
}
}
});
if (stationHit != entt::null)
{
return stationHit;
}
constexpr float kShipHitRadiusSquared = 0.5f * 0.5f;
entt::entity bestShip = entt::null;
float bestDistSquared = kShipHitRadiusSquared;
admin.forEach<PositionComponent, HealthComponent>(
[&](entt::entity entity, const PositionComponent& pos, const HealthComponent& h)
{
if (h.hp <= 0.0f) { return; }
if (admin.hasAll<StationBodyComponent>(entity)) { return; }
const float dx = pos.value.x() - worldPos.x();
const float dy = pos.value.y() - worldPos.y();
const float distSquared = dx * dx + dy * dy;
if (distSquared < bestDistSquared)
{
bestDistSquared = distSquared;
bestShip = entity;
}
});
return bestShip;
}
entt::entity debrisAtWorldPos(EntityAdmin& admin, QVector2D worldPos)
{
// Slightly larger than the debris's rendered radius (0.2 tiles) so small pieces
// remain easy to click; tunable.
constexpr float kDebrisHitRadiusSquared = 0.35f * 0.35f;
entt::entity bestDebris = entt::null;
float bestDistSquared = kDebrisHitRadiusSquared;
admin.forEach<DebrisComponent, PositionComponent>(
[&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos)
{
const float dx = pos.value.x() - worldPos.x();
const float dy = pos.value.y() - worldPos.y();
const float distSquared = dx * dx + dy * dy;
if (distSquared < bestDistSquared)
{
bestDistSquared = distSquared;
bestDebris = entity;
}
});
return bestDebris;
}
std::vector<entt::entity> debrisInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB)
{
const int minX = std::min(tileA.x(), tileB.x());
const int maxX = std::max(tileA.x(), tileB.x());
const int minY = std::min(tileA.y(), tileB.y());
const int maxY = std::max(tileA.y(), tileB.y());
std::vector<entt::entity> result;
admin.forEach<DebrisComponent, PositionComponent>(
[&](entt::entity entity, const DebrisComponent& /*sd*/, const PositionComponent& pos)
{
const int tileX = static_cast<int>(std::floor(pos.value.x()));
const int tileY = static_cast<int>(std::floor(pos.value.y()));
if (tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY)
{
result.push_back(entity);
}
});
return result;
}
std::vector<entt::entity> actorsInBox(EntityAdmin& admin, QPoint tileA, QPoint tileB)
{
const int minX = std::min(tileA.x(), tileB.x());
const int maxX = std::max(tileA.x(), tileB.x());
const int minY = std::min(tileA.y(), tileB.y());
const int maxY = std::max(tileA.y(), tileB.y());
std::vector<entt::entity> result;
// Stations: included when any occupied body cell lies in the box.
admin.forEach<StationBodyComponent, HealthComponent>(
[&](entt::entity entity, const StationBodyComponent& sb, const HealthComponent& h)
{
if (h.hp <= 0.0f) { return; }
for (const QPoint& cell : sb.bodyCells)
{
if (cell.x() >= minX && cell.x() <= maxX
&& cell.y() >= minY && cell.y() <= maxY)
{
result.push_back(entity);
return;
}
}
});
// Ships: included when the floored position tile lies in the box. Requiring
// ShipIdentityComponent excludes the HQ proxy and any station bodies.
admin.forEach<ShipIdentityComponent, PositionComponent, HealthComponent>(
[&](entt::entity entity, const ShipIdentityComponent& /*id*/,
const PositionComponent& pos, const HealthComponent& h)
{
if (h.hp <= 0.0f) { return; }
const int tileX = static_cast<int>(std::floor(pos.value.x()));
const int tileY = static_cast<int>(std::floor(pos.value.y()));
if (tileX >= minX && tileX <= maxX && tileY >= minY && tileY <= maxY)
{
result.push_back(entity);
}
});
return result;
}