#include "SelectionContentFactory.h" #include "AutoProductionContent.h" #include "BeltContent.h" #include "DebrisContent.h" #include "FactoryQueries.h" #include "FieldMultiContent.h" #include "HqContent.h" #include "MultiBuildingContent.h" #include "RecipeProductionContent.h" #include "ShipContent.h" #include "ShipIdentityComponent.h" #include "ShipyardContent.h" #include "SplitterContent.h" #include "StationBodyComponent.h" #include "StationContent.h" #include "StorageContent.h" #include "Simulation.h" namespace { // The catalog row a single building type belongs to (REQ-UI-SELECTION-CONTENT). SelectionContentKind getKindForType(BuildingType type) { switch (type) { case BuildingType::Miner: case BuildingType::Assembler: return SelectionContentKind::RecipeProduction; case BuildingType::Smelter: case BuildingType::ReprocessingPlant: return SelectionContentKind::AutoProduction; case BuildingType::Shipyard: return SelectionContentKind::Shipyard; case BuildingType::SalvageBay: return SelectionContentKind::Storage; case BuildingType::Hq: return SelectionContentKind::Hq; case BuildingType::Belt: case BuildingType::TunnelEntry: case BuildingType::TunnelExit: return SelectionContentKind::Belt; case BuildingType::Splitter: return SelectionContentKind::Splitter; case BuildingType::PlayerDefenceStation: case BuildingType::EnemyDefenceStation: // Defence stations are field objects backed by entities; these two enum // values exist only for cost and visuals lookup and never reach the panel as // a building selection. The count summary is the harmless fallback. return SelectionContentKind::MultiBuilding; } return SelectionContentKind::MultiBuilding; } // A belt, tunnel entry or tunnel exit -- the types whose card is the clear action alone // and therefore aggregates (REQ-UI-SELECTION-AGGREGATE). The splitter is deliberately // excluded: it carries per-object output filters, which have no aggregate. bool isAggregatableBeltType(BuildingType type) { return type == BuildingType::Belt || type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit; } ContentKey chooseBuildingContent(const SelectionRequest& request, Simulation& sim) { const FactoryState& state = sim.getFactoryState(); if (request.buildings.size() == 1) { const BuildingId id = request.buildings.front(); const Building* building = findBuilding(state, id); if (building) { return { getKindForType(building->type), false }; } const ConstructionSite* site = findSite(state, id); if (site) { return { getKindForType(site->type), true }; } // The building went away under the panel; the selection is stale and there is // nothing to show (REQ-UI-EMPTY-SELECTION). return {}; } // Several buildings aggregate into one card only when every part of that card // aggregates (REQ-UI-SELECTION-AGGREGATE). Construction sites are excluded from the // belt case: a site's card is its own construction progress, which several sites // cannot share. bool allAggregatableBelts = true; for (BuildingId id : request.buildings) { const Building* building = findBuilding(state, id); if (!building || !isAggregatableBeltType(building->type)) { allAggregatableBelts = false; break; } } if (allAggregatableBelts) { return { SelectionContentKind::Belt, false }; } return { SelectionContentKind::MultiBuilding, false }; } ContentKey chooseFieldContent(const SelectionRequest& request, Simulation& sim) { // A full single-object card is shown for a lone actor, and for debris whether one // piece or several -- debris is the field category's other aggregating content // (REQ-UI-FIELD-MULTI-SELECTION, REQ-UI-SELECTION-AGGREGATE). if (request.actors.size() == 1 && request.debris.empty()) { EntityAdmin& admin = sim.getAdmin(); const entt::entity actor = request.actors.front(); if (admin.isValid(actor) && admin.hasAll(actor)) { return { SelectionContentKind::Ship, false }; } if (admin.isValid(actor) && admin.hasAll(actor)) { return { SelectionContentKind::Station, false }; } return {}; } if (request.actors.empty() && !request.debris.empty()) { return { SelectionContentKind::Debris, false }; } return { SelectionContentKind::FieldMulti, false }; } } // namespace ContentKey chooseContent(const SelectionRequest& request, Simulation& sim) { if (request.isEmpty()) { return {}; } // Buildings win, so a non-empty building selection is the whole selection // (REQ-UI-SELECTION-CATEGORIES). if (!request.buildings.empty()) { return chooseBuildingContent(request, sim); } return chooseFieldContent(request, sim); } SelectionContent* createContent(const ContentKey& key, const SelectionRequest& request, const SelectionContext& context, QWidget* parent) { switch (key.kind) { case SelectionContentKind::None: return nullptr; case SelectionContentKind::RecipeProduction: return new RecipeProductionContent(context, request, parent); case SelectionContentKind::AutoProduction: return new AutoProductionContent(context, request, parent); case SelectionContentKind::Shipyard: return new ShipyardContent(context, request, parent); case SelectionContentKind::Storage: return new StorageContent(context, request, parent); case SelectionContentKind::Hq: return new HqContent(context, request, parent); case SelectionContentKind::Belt: return new BeltContent(context, request, parent); case SelectionContentKind::Splitter: return new SplitterContent(context, request, parent); case SelectionContentKind::MultiBuilding: return new MultiBuildingContent(context, request, parent); case SelectionContentKind::Ship: return new ShipContent(context, request, parent); case SelectionContentKind::Station: return new StationContent(context, request, parent); case SelectionContentKind::Debris: return new DebrisContent(context, request, parent); case SelectionContentKind::FieldMulti: return new FieldMultiContent(context, request, parent); } return nullptr; }