#include "SalvagerSystem.h" #include "FactoryQueries.h" #include #include #include "Building.h" #include "BuildingSystem.h" #include "CargoComponent.h" #include "DeliverScrapBehavior.h" #include "EntityAdmin.h" #include #include "ModuleOwnerComponent.h" #include "PositionComponent.h" #include "SalvagerComponent.h" #include "DebrisComponent.h" #include "DebrisSystem.h" #include "tracing.h" SalvagerSystem::SalvagerSystem(EntityAdmin& admin) : m_admin(admin) { } void SalvagerSystem::tick(Tick currentTick, FactoryState& state, std::vector& outBeamFiredEvents) { TRACE(); // Apply collections whose mid-beam delay has elapsed (cycles started earlier). applyPendingCollections(currentTick); const std::vector allDebris = getAllDebrisInfo(m_admin); // Tick down per-module collection cooldowns. m_admin.forEach( [](entt::entity /*e*/, SalvagerComponent& s) { if (s.cooldownTicksRemaining > 0) { --s.cooldownTicksRemaining; } }); // Scrap units already claimed by not-yet-applied collection cycles, so two // modules don't both target the last unit of the same debris (the claim would be // dropped at apply time). Debris is available while its amount exceeds its claims. std::map claimedUnits; // Collection cycles already in flight toward each ship's shared cargo pool, so // concurrent modules on the same ship never start more cycles than the remaining // capacity can hold (REQ-SHP-SALVAGE). std::map pendingByShip; for (const PendingCollection& pc : m_pendingCollections) { ++claimedUnits[pc.debris]; ++pendingByShip[pc.ship]; } // Cycle start: each ready, in-range module whose ship's pool has free space begins // a collection cycle — emit the beam now, collect one scrap mid-beam, start the // cooldown now. m_admin.forEach( [&](entt::entity /*moduleEntity*/, SalvagerComponent& s, const ModuleOwnerComponent& o) { if (s.cooldownTicksRemaining > 0 || s.collectionIntervalTicks <= 0) { return; } if (!m_admin.hasAll(o.owner)) { return; } const CargoComponent& cargo = m_admin.get(o.owner); if (cargo.current + pendingByShip[o.owner] >= cargo.maxCapacity) { return; } const QVector2D ownerPos = m_admin.get(o.owner).value; for (const DebrisInfo& si : allDebris) { if ((si.position - ownerPos).length() > s.collectionRange_tiles) { continue; } if (claimedUnits[si.entity] >= m_admin.get(si.entity).amount) { continue; // every remaining unit of this debris is already spoken for } outBeamFiredEvents.push_back( BeamFiredEvent{BeamKind::Salvage, o.owner, si.entity, currentTick}); m_pendingCollections.push_back({o.owner, si.entity, currentTick + kBeamImpactDelayTicks}); ++claimedUnits[si.entity]; ++pendingByShip[o.owner]; s.cooldownTicksRemaining = s.collectionIntervalTicks; break; } }); // Delivery: a ship at its assigned bay hands over one unit of cargo per tick. m_admin.forEach( [&](entt::entity ship, const DeliverScrapBehavior& deliver, const PositionComponent& pos) { if (!deliver.deliveryBay.has_value()) { return; } const Building* bay = findBuilding(state, *deliver.deliveryBay); if (!bay) { return; } const QVector2D bayCenter(bay->anchor.x() + bay->footprint.width() / 2.0f, bay->anchor.y() + bay->footprint.height() / 2.0f); if ((pos.value - bayCenter).length() > 1.0f) { return; } // Hand over one unit from the ship's shared cargo pool. if (!m_admin.hasAll(ship)) { return; } CargoComponent& cargo = m_admin.get(ship); if (cargo.current <= 0) { return; } if (deliverScrapToSalvageBay(state, *deliver.deliveryBay)) { --cargo.current; } }); } void SalvagerSystem::applyPendingCollections(Tick currentTick) { std::vector::iterator it = m_pendingCollections.begin(); while (it != m_pendingCollections.end()) { if (it->appliesAt <= currentTick) { if (m_admin.isValid(it->ship) && m_admin.hasAll(it->ship)) { CargoComponent& cargo = m_admin.get(it->ship); if (cargo.current < cargo.maxCapacity && collectOne(m_admin, it->debris)) { ++cargo.current; } } it = m_pendingCollections.erase(it); } else { ++it; } } }