list what a belt is carrying, and clear it by name
The belt card's only content was a button reading "Clear stuck items", which assumed a state the items need not be in. It now reads "Clear items", and above it the card lists what the selected tiles hold -- one item chip per type, the same chip the buffer sections and the HQ's block stock draw -- so a line's contents can be read before they are removed, and can be read at all: items on a moving belt are too small and too transient to count by eye, and items inside a tunnel are drawn nowhere. BeltSystem gains countItems(tiles), a query of the same kind as forEachVisualItem: a method rather than exposed tile containers, so the per-tile representation stays swappable. It walks the same five containers as clearTiles, in the same order, so the list and the button cannot drift apart. The tunnel's two ends are now told apart. Items in transit are counted on the exit they are travelling toward, and a clear removes exactly what the panel listed for the tile it acts on: the exit discards them, the entry leaves them travelling. BeltSystemTest's tunnel case splits in two accordingly. Splitters now aggregate with belts and tunnel ends. Their output filters are per-object configuration, which an aggregate simply does not show -- a splitter selected alone still gets them. Both cards share one BeltItemList widget, and both derive their tiles from collectBeltTiles, so nothing is stated twice. The mixed count summary loses the clear action it carried: a button acting on part of a selection is worse than no button, and the tiles can be selected by themselves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -190,6 +190,10 @@ public:
|
|||||||
void forEachVisualItem(QRect viewportTiles,
|
void forEachVisualItem(QRect viewportTiles,
|
||||||
std::function<void(VisualItem)> visit) const;
|
std::function<void(VisualItem)> visit) const;
|
||||||
|
|
||||||
|
// Inspection
|
||||||
|
std::map<ItemType, int> countItems( // REQ-UI-BELT-ITEMS
|
||||||
|
const std::vector<QPoint>& tiles) const; // transit items on the exit
|
||||||
|
|
||||||
// Determinism (docs/replay_design.md)
|
// Determinism (docs/replay_design.md)
|
||||||
void appendChecksum(Hasher& hasher) const;
|
void appendChecksum(Hasher& hasher) const;
|
||||||
};
|
};
|
||||||
@@ -200,7 +204,7 @@ struct VisualItem {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Item *transport* is still reached only through push and pull: `tryPutItem` / `tryTakeItem` move items, `peekItem` reveals the leading item's type but never an identity, and rendering reads only through `forEachVisualItem`. The growth is in tile **topology** — placement, removal and splitter filters — which `BuildingSystem` drives because belts are `Building`s for cost, construction and deconstruction. That coupling is real and is not going away.
|
Item *transport* is still reached only through push and pull: `tryPutItem` / `tryTakeItem` move items, `peekItem` reveals the leading item's type but never an identity, and rendering reads only through `forEachVisualItem`. `countItems` is a query of the same kind, added for the selection panel's item list (REQ-UI-BELT-ITEMS): it answers *how many of what* over a set of tiles and is deliberately a method rather than exposed tile containers, which would freeze the per-tile representation into its callers and cost the v2 migration described below. The growth is in tile **topology** — placement, removal and splitter filters — which `BuildingSystem` drives because belts are `Building`s for cost, construction and deconstruction. That coupling is real and is not going away.
|
||||||
|
|
||||||
### Implementation Strategy
|
### Implementation Strategy
|
||||||
|
|
||||||
|
|||||||
@@ -639,7 +639,7 @@ The panel shows exactly one **content** at a time, picked from the catalog in RE
|
|||||||
Selections sharing a row of this table get the same content and differ only in the name and symbol in the header. A count in the right slot appears only for an aggregated multi-selection (REQ-UI-SELECTION-AGGREGATE); a single selection of those types shows an empty slot.
|
Selections sharing a row of this table get the same content and differ only in the name and symbol in the header. A count in the right slot appears only for an aggregated multi-selection (REQ-UI-SELECTION-AGGREGATE); a single selection of those types shows an empty slot.
|
||||||
- REQ-UI-SELECTION-STATUS: **Status indicator.** For a building whose production state is already rendered in the world as a status light (REQ-UI-STATUS-LIGHT) — Miner, Smelter, Assembler, Reprocessing Plant, Shipyard, Salvage Bay — the header's right slot repeats that same state as a colored dot with a short caption beside it, so the panel and the world never disagree. The state is derived from the evaluation defined in REQ-UI-STATUS-LIGHT rather than from a second definition, and the dot uses that state's fill color from `visuals.toml [status_light]`. The captions name the state: `no recipe` (grey), `producing` (green), `missing input` (red), `output full` (yellow); for the Salvage Bay, `holding scrap` (green) and `empty` (red). A selected **construction site** shows the caption `constructing` with no dot, whatever its type. Buildings with no status light — belts, splitters, tunnel ends, the HQ — show nothing in the slot.
|
- REQ-UI-SELECTION-STATUS: **Status indicator.** For a building whose production state is already rendered in the world as a status light (REQ-UI-STATUS-LIGHT) — Miner, Smelter, Assembler, Reprocessing Plant, Shipyard, Salvage Bay — the header's right slot repeats that same state as a colored dot with a short caption beside it, so the panel and the world never disagree. The state is derived from the evaluation defined in REQ-UI-STATUS-LIGHT rather than from a second definition, and the dot uses that state's fill color from `visuals.toml [status_light]`. The captions name the state: `no recipe` (grey), `producing` (green), `missing input` (red), `output full` (yellow); for the Salvage Bay, `holding scrap` (green) and `empty` (red). A selected **construction site** shows the caption `constructing` with no dot, whatever its type. Buildings with no status light — belts, splitters, tunnel ends, the HQ — show nothing in the slot.
|
||||||
- REQ-UI-SELECTION-AGGREGATE: **Aggregating a homogeneous multi-selection.** When several objects are selected and their content can be shown as one — the same content, with its values aggregated over the whole selection — the panel shows that single content with the number of selected objects in the header's right slot (`x<count>`), instead of the count summary of REQ-UI-MULTI-SELECTION / REQ-UI-FIELD-MULTI-SELECTION. This applies where every part of the **runtime** content aggregates. A **per-object configuration** does not stand in the way: it is simply not shown, there being no single object for it to configure.
|
- REQ-UI-SELECTION-AGGREGATE: **Aggregating a homogeneous multi-selection.** When several objects are selected and their content can be shown as one — the same content, with its values aggregated over the whole selection — the panel shows that single content with the number of selected objects in the header's right slot (`x<count>`), instead of the count summary of REQ-UI-MULTI-SELECTION / REQ-UI-FIELD-MULTI-SELECTION. This applies where every part of the **runtime** content aggregates. A **per-object configuration** does not stand in the way: it is simply not shown, there being no single object for it to configure.
|
||||||
- **Belt-subsystem tiles** — any mix of belts, tunnel entries, tunnel exits, and splitters. Their content is the item list and the clear action (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR), both of which already read and act on the whole selection. A splitter's output filters are per-object, so they are shown only when that splitter is the only selected object (REQ-BLD-SPLITTER); an aggregate holding one or more splitters shows no filters at all. The card is named after the type of the **first tile** in the selection, whatever the mix, and the count in the right slot says how many are held.
|
- **Belt-subsystem tiles** — any mix of belts, tunnel entries, tunnel exits, and splitters. Their content is the item list and the clear action (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR), both of which already read and act on the whole selection. A splitter's output filters are per-object, so they are shown only when that splitter is the only selected object (REQ-BLD-SPLITTER); an aggregate holding one or more splitters shows no filters at all. The card is named after the type of the **first tile** in the selection, whatever the mix, and the count in the right slot says how many are held. **Construction sites do not aggregate**: a selection holding one falls back to the count summary, a site having neither items on it nor a tile the belt subsystem knows (REQ-BLD-SITE-CONFIG).
|
||||||
- **Debris** — several pieces of debris and nothing else. Their remaining scrap sums into one value (REQ-UI-DEBRIS-PANEL).
|
- **Debris** — several pieces of debris and nothing else. Their remaining scrap sums into one value (REQ-UI-DEBRIS-PANEL).
|
||||||
|
|
||||||
Every other multi-selection falls back to the count summary. In particular several production buildings of one type — three miners, say — still do not aggregate, and the relaxation above does not change that: what stops them is their **runtime** content, not their recipe control. A buffer chip states a count against *that building's* capacity (`a / b`, REQ-MAT-OUTPUT-BUFFER) and a production section states *one* cycle's completion; summing three miners' ore into one chip would state a figure no buffer holds and no belt can draw, and averaging three cycle percentages would describe no building. Only where the runtime content is genuinely a property of the whole selection — items lying on a set of tiles, scrap remaining across a set of debris — is there one card to show.
|
Every other multi-selection falls back to the count summary. In particular several production buildings of one type — three miners, say — still do not aggregate, and the relaxation above does not change that: what stops them is their **runtime** content, not their recipe control. A buffer chip states a count against *that building's* capacity (`a / b`, REQ-MAT-OUTPUT-BUFFER) and a production section states *one* cycle's completion; summing three miners' ore into one chip would state a figure no buffer holds and no belt can draw, and averaging three cycle percentages would describe no building. Only where the runtime content is genuinely a property of the whole selection — items lying on a set of tiles, scrap remaining across a set of debris — is there one card to show.
|
||||||
@@ -676,7 +676,7 @@ The panel shows exactly one **content** at a time, picked from the catalog in RE
|
|||||||
- REQ-UI-BELT-ITEMS: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel's runtime group shows a captioned **`Items`** section stating what those tiles are carrying, above the clear action (REQ-UI-BELT-CLEAR) — the player reads what is there before removing it. Each item type present is shown as an **item chip** bearing the item's icon on its colored square (REQ-UI-ITEM-ICON) with the number of units held and the item's name below it, and hovering or clicking the chip shows that item's production tooltip (REQ-UI-ITEM-TOOLTIP) — the same chip the buffer sections and the HQ's block stock use (REQ-UI-SINGLE-SELECTION, REQ-UI-HQ-PANEL). This is what makes a belt's contents readable at all: on a moving line the items are too small and too transient to count by eye, and inside a tunnel they cannot be seen.
|
- REQ-UI-BELT-ITEMS: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel's runtime group shows a captioned **`Items`** section stating what those tiles are carrying, above the clear action (REQ-UI-BELT-CLEAR) — the player reads what is there before removing it. Each item type present is shown as an **item chip** bearing the item's icon on its colored square (REQ-UI-ITEM-ICON) with the number of units held and the item's name below it, and hovering or clicking the chip shows that item's production tooltip (REQ-UI-ITEM-TOOLTIP) — the same chip the buffer sections and the HQ's block stock use (REQ-UI-SINGLE-SELECTION, REQ-UI-HQ-PANEL). This is what makes a belt's contents readable at all: on a moving line the items are too small and too transient to count by eye, and inside a tunnel they cannot be seen.
|
||||||
- **Counts sum over the whole selection**, so there is one chip per item type however many tiles are held, matching the clear action's reach (REQ-UI-SELECTION-AGGREGATE).
|
- **Counts sum over the whole selection**, so there is one chip per item type however many tiles are held, matching the clear action's reach (REQ-UI-SELECTION-AGGREGATE).
|
||||||
- **Items in transit through a tunnel** (REQ-BLD-TUNNEL-TRANSIT) are counted on the **tunnel exit**, that being where they will arrive; a selected tunnel entry counts only what is on its own tile. Selecting the exit is therefore how the player sees what a tunnel is currently swallowing, and how a tunnel is emptied (REQ-UI-BELT-CLEAR).
|
- **Items in transit through a tunnel** (REQ-BLD-TUNNEL-TRANSIT) are counted on the **tunnel exit**, that being where they will arrive; a selected tunnel entry counts only what is on its own tile. Selecting the exit is therefore how the player sees what a tunnel is currently swallowing, and how a tunnel is emptied (REQ-UI-BELT-CLEAR).
|
||||||
- Unlike a building's buffers, a belt has no fixed set of item types it can hold, so the section lists only the types actually present and shows no `0` chips. The chips keep the order the items are declared in config, so a chip does not jump about as counts change. When the selection carries nothing at all the section stays and reads the note `No items`, so the card holds one shape while items come and go.
|
- Unlike a building's buffers, a belt has no fixed set of item types it can hold, so the section lists only the types actually present and shows no `0` chips. The chips are ordered by **item id**, so a chip does not jump about as counts change — config declares no global item list to take an order from, items being named only where recipes and visuals use them. When the selection carries nothing at all the section stays and reads the note `No items`, so the card holds one shape while items come and go.
|
||||||
- REQ-UI-BELT-CLEAR: Below that list, the panel's runtime group shows a **"Clear items"** button that removes all items from the selected tiles. It is named for what it does rather than for a state it assumes: the items on a belt need not be stuck, and emptying a line is just as often what the player wants of a line that is running. It removes exactly what the list above it shows, tile for tile: clearing a **tunnel exit** therefore also discards the items in transit through that tunnel, which are counted on the exit, while clearing a **tunnel entry** leaves them travelling (REQ-BLD-TUNNEL-TRANSIT, REQ-UI-BELT-ITEMS). This can be used to resolve stalled belts, splitters, and tunnels. The button acts on every selected tile, which is why any mix of belt-subsystem tiles aggregates into one content rather than a count summary (REQ-UI-SELECTION-AGGREGATE).
|
- REQ-UI-BELT-CLEAR: Below that list, the panel's runtime group shows a **"Clear items"** button that removes all items from the selected tiles. It is named for what it does rather than for a state it assumes: the items on a belt need not be stuck, and emptying a line is just as often what the player wants of a line that is running. It removes exactly what the list above it shows, tile for tile: clearing a **tunnel exit** therefore also discards the items in transit through that tunnel, which are counted on the exit, while clearing a **tunnel entry** leaves them travelling (REQ-BLD-TUNNEL-TRANSIT, REQ-UI-BELT-ITEMS). This can be used to resolve stalled belts, splitters, and tunnels. The button acts on every selected tile, which is why any mix of belt-subsystem tiles aggregates into one content rather than a count summary (REQ-UI-SELECTION-AGGREGATE).
|
||||||
|
|
||||||
Both the list and the button appear only where the selection holds **nothing but** belt-subsystem tiles. A selection mixing them with other buildings shows the count summary (REQ-UI-MULTI-SELECTION) and carries neither: a button that silently acted on part of a selection would be worse than no button, and the player can select the tiles alone.
|
Both the list and the button appear only where the selection holds **nothing but** belt-subsystem tiles. A selection mixing them with other buildings shows the count summary (REQ-UI-MULTI-SELECTION) and carries neither: a button that silently acted on part of a selection would be worse than no button, and the player can select the tiles alone.
|
||||||
|
|||||||
@@ -408,14 +408,11 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
m_tunnelEntries.find(key(tile));
|
m_tunnelEntries.find(key(tile));
|
||||||
if (teIt != m_tunnelEntries.end())
|
if (teIt != m_tunnelEntries.end())
|
||||||
{
|
{
|
||||||
|
// Only what sits on the entry itself: the items already inside the tunnel
|
||||||
|
// are counted on its exit (REQ-UI-BELT-ITEMS) and are cleared from there, so
|
||||||
|
// a clear never removes more than the panel showed for the tile it acted on
|
||||||
|
// (REQ-BLD-TUNNEL-TRANSIT). Emptying a tunnel is done from its exit.
|
||||||
teIt->second.itemSlots.clear();
|
teIt->second.itemSlots.clear();
|
||||||
for (TunnelLink& link : m_tunnelLinks)
|
|
||||||
{
|
|
||||||
if (link.entryTile == tile)
|
|
||||||
{
|
|
||||||
link.items.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||||
@@ -434,6 +431,83 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<ItemType, int> BeltSystem::countItems(const std::vector<QPoint>& tiles) const
|
||||||
|
{
|
||||||
|
std::map<ItemType, int> counts;
|
||||||
|
|
||||||
|
// The same five containers clearTiles walks, in the same order, so that what the
|
||||||
|
// panel lists and what the button removes cannot drift apart (REQ-UI-BELT-ITEMS,
|
||||||
|
// REQ-UI-BELT-CLEAR).
|
||||||
|
for (const QPoint& tile : tiles)
|
||||||
|
{
|
||||||
|
const std::map<std::pair<int, int>, BeltTile>::const_iterator bIt =
|
||||||
|
m_belts.find(key(tile));
|
||||||
|
if (bIt != m_belts.end())
|
||||||
|
{
|
||||||
|
for (const BeltItemSlot& slot : bIt->second.itemSlots)
|
||||||
|
{
|
||||||
|
counts[slot.item.type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::map<std::pair<int, int>, SplitterTile>::const_iterator sIt =
|
||||||
|
m_splitters.find(key(tile));
|
||||||
|
if (sIt != m_splitters.end())
|
||||||
|
{
|
||||||
|
const SplitterTile& splitter = sIt->second;
|
||||||
|
for (const BeltItemSlot& slot : splitter.back)
|
||||||
|
{
|
||||||
|
counts[slot.item.type]++;
|
||||||
|
}
|
||||||
|
if (splitter.frontA)
|
||||||
|
{
|
||||||
|
counts[splitter.frontA->item.type]++;
|
||||||
|
}
|
||||||
|
if (splitter.frontB)
|
||||||
|
{
|
||||||
|
counts[splitter.frontB->item.type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::map<std::pair<int, int>, TunnelEntryTile>::const_iterator teIt =
|
||||||
|
m_tunnelEntries.find(key(tile));
|
||||||
|
if (teIt != m_tunnelEntries.end())
|
||||||
|
{
|
||||||
|
for (const BeltItemSlot& slot : teIt->second.itemSlots)
|
||||||
|
{
|
||||||
|
counts[slot.item.type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::map<std::pair<int, int>, TunnelExitTile>::const_iterator txIt =
|
||||||
|
m_tunnelExits.find(key(tile));
|
||||||
|
if (txIt != m_tunnelExits.end())
|
||||||
|
{
|
||||||
|
for (const BeltItemSlot& slot : txIt->second.itemSlots)
|
||||||
|
{
|
||||||
|
counts[slot.item.type]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The tunnel's invisible cargo, counted on the end it is travelling toward.
|
||||||
|
// This is the only place the player can see it at all, the tunnel drawing
|
||||||
|
// nothing of what is inside it (REQ-BLD-TUNNEL-TRANSIT).
|
||||||
|
for (const TunnelLink& link : m_tunnelLinks)
|
||||||
|
{
|
||||||
|
if (link.exitTile != tile)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const TunnelTransitItem& transit : link.items)
|
||||||
|
{
|
||||||
|
counts[transit.item.type]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Tick
|
// Tick
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -95,13 +95,26 @@ public:
|
|||||||
double getProgressPerTick_tpt() const { return m_progressPerTick_tpt; }
|
double getProgressPerTick_tpt() const { return m_progressPerTick_tpt; }
|
||||||
|
|
||||||
// -- Maintenance ---------------------------------------------------------
|
// -- Maintenance ---------------------------------------------------------
|
||||||
void clearTiles(const std::vector<QPoint>& tiles); // REQ-UI-BELT-CLEAR
|
// Removes every item from the given tiles (REQ-UI-BELT-CLEAR). A tunnel exit takes
|
||||||
|
// the items in transit through its tunnel with it; a tunnel entry does not, so what
|
||||||
|
// a clear removes is exactly what countItems reported for the same tile.
|
||||||
|
void clearTiles(const std::vector<QPoint>& tiles);
|
||||||
void tick();
|
void tick();
|
||||||
|
|
||||||
// -- Rendering -----------------------------------------------------------
|
// -- Rendering -----------------------------------------------------------
|
||||||
void forEachVisualItem(QRect viewportTiles,
|
void forEachVisualItem(QRect viewportTiles,
|
||||||
std::function<void(VisualItem)> visit) const;
|
std::function<void(VisualItem)> visit) const;
|
||||||
|
|
||||||
|
// -- Inspection ----------------------------------------------------------
|
||||||
|
// What the given tiles carry, summed per item type (REQ-UI-BELT-ITEMS). Items in
|
||||||
|
// transit through a tunnel are counted on its exit, that being where they will
|
||||||
|
// arrive and the only end whose clear discards them (REQ-BLD-TUNNEL-TRANSIT).
|
||||||
|
//
|
||||||
|
// A method rather than an exposed container: what a tile holds and how it holds it
|
||||||
|
// is this subsystem's alone to know, so the per-tile representation stays swappable
|
||||||
|
// (architecture.md, Belt Subsystem).
|
||||||
|
std::map<ItemType, int> countItems(const std::vector<QPoint>& tiles) const;
|
||||||
|
|
||||||
// -- Determinism ---------------------------------------------------------
|
// -- Determinism ---------------------------------------------------------
|
||||||
// Folds all transport state (belt/splitter/tunnel tiles and their items)
|
// Folds all transport state (belt/splitter/tunnel tiles and their items)
|
||||||
// into the hasher in deterministic order (see docs/replay_design.md).
|
// into the hasher in deterministic order (see docs/replay_design.md).
|
||||||
|
|||||||
@@ -233,3 +233,25 @@ TunnelTileMap collectTunnelTiles(const FactoryState& state)
|
|||||||
}
|
}
|
||||||
return tunnels;
|
return tunnels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<QPoint> collectBeltTiles(const FactoryState& state,
|
||||||
|
const std::vector<BuildingId>& ids)
|
||||||
|
{
|
||||||
|
std::vector<QPoint> tiles;
|
||||||
|
for (BuildingId id : ids)
|
||||||
|
{
|
||||||
|
// findBuilding fails for a construction site, which is what keeps a site out:
|
||||||
|
// its tile carries nothing and is not registered with the belt subsystem until
|
||||||
|
// it is built (REQ-BLD-SITE-CONFIG).
|
||||||
|
const Building* building = findBuilding(state, id);
|
||||||
|
if (!building || !isBeltSubsystemType(building->type))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const QPoint& cell : building->bodyCells)
|
||||||
|
{
|
||||||
|
tiles.push_back(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,3 +82,11 @@ std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
|||||||
// Every tunnel entry and exit, built or still a construction site, indexed by its
|
// Every tunnel entry and exit, built or still a construction site, indexed by its
|
||||||
// single-cell tile. Shared by the placement preview and the selection highlight.
|
// single-cell tile. Shared by the placement preview and the selection highlight.
|
||||||
TunnelTileMap collectTunnelTiles(const FactoryState& state);
|
TunnelTileMap collectTunnelTiles(const FactoryState& state);
|
||||||
|
|
||||||
|
// The tiles of the belt-subsystem buildings among the given ids, in the order the ids
|
||||||
|
// arrive; anything else in the selection is skipped, as are construction sites, whose
|
||||||
|
// tiles the belt subsystem does not know yet (REQ-BLD-SITE-CONFIG). Shared by the panel's
|
||||||
|
// item list and its clear action, so both speak of exactly the same tiles
|
||||||
|
// (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
|
||||||
|
std::vector<QPoint> collectBeltTiles(const FactoryState& state,
|
||||||
|
const std::vector<BuildingId>& ids);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -327,6 +328,87 @@ TEST_CASE("BeltSystem: forEachVisualItem reports correct ItemType", "[belt]")
|
|||||||
REQUIRE(seen[0].id == "copper_ingot");
|
REQUIRE(seen[0].id == "copper_ingot");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// countItems (REQ-UI-BELT-ITEMS)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: countItems sums one item type over several tiles", "[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
const QPoint first(0, 0);
|
||||||
|
const QPoint second(1, 0);
|
||||||
|
bs.placeBelt(first, Rotation::East);
|
||||||
|
bs.placeBelt(second, Rotation::East);
|
||||||
|
bs.tryPutItem(first, makeItem("iron_ore"), Rotation::East);
|
||||||
|
bs.tryPutItem(second, makeItem("iron_ore"), Rotation::East);
|
||||||
|
bs.tryPutItem(second, makeItem("copper_ore"), Rotation::East);
|
||||||
|
|
||||||
|
const std::map<ItemType, int> counts = bs.countItems({first, second});
|
||||||
|
|
||||||
|
REQUIRE(counts.size() == 2);
|
||||||
|
REQUIRE(counts.at(ItemType{"iron_ore"}) == 2);
|
||||||
|
REQUIRE(counts.at(ItemType{"copper_ore"}) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: countItems ignores tiles outside the given selection", "[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
const QPoint selected(0, 0);
|
||||||
|
const QPoint other(1, 0);
|
||||||
|
bs.placeBelt(selected, Rotation::East);
|
||||||
|
bs.placeBelt(other, Rotation::East);
|
||||||
|
bs.tryPutItem(other, makeItem("iron_ore"), Rotation::East);
|
||||||
|
|
||||||
|
REQUIRE(bs.countItems({selected}).empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: countItems counts a splitter's held items", "[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
const QPoint feed(0, 0);
|
||||||
|
const QPoint splitter(1, 0);
|
||||||
|
bs.placeBelt(feed, Rotation::East);
|
||||||
|
bs.placeSplitter(splitter, Rotation::North, Rotation::South);
|
||||||
|
|
||||||
|
// One item routed onto an output slot, one still unassigned in the back.
|
||||||
|
bs.tryPutItem(splitter, makeItem("iron_ore"), Rotation::East);
|
||||||
|
bs.tick();
|
||||||
|
bs.tryPutItem(splitter, makeItem("iron_ore"), Rotation::East);
|
||||||
|
|
||||||
|
REQUIRE(bs.countItems({splitter}).at(ItemType{"iron_ore"}) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: countItems counts transit items on the exit, not the entry",
|
||||||
|
"[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
|
||||||
|
const QPoint entry(0, 0);
|
||||||
|
const QPoint exit(5, 0);
|
||||||
|
|
||||||
|
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||||
|
bs.placeTunnelExit(exit, Rotation::East);
|
||||||
|
|
||||||
|
bs.tryPutItem(entry, makeItem("iron_ore"), Rotation::East);
|
||||||
|
bs.tick(); // item enters the entry's front slot
|
||||||
|
bs.tick(); // entry front -> transit
|
||||||
|
|
||||||
|
// Invisible in the world and drawn nowhere, so the exit's count is the only place
|
||||||
|
// the player can see it (REQ-BLD-TUNNEL-TRANSIT).
|
||||||
|
REQUIRE(bs.countItems({entry}).empty());
|
||||||
|
REQUIRE(bs.countItems({exit}).at(ItemType{"iron_ore"}) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: countItems on empty tiles reports nothing", "[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
const QPoint tile(0, 0);
|
||||||
|
bs.placeBelt(tile, Rotation::East);
|
||||||
|
|
||||||
|
REQUIRE(bs.countItems({tile}).empty());
|
||||||
|
REQUIRE(bs.countItems({}).empty());
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Splitter — basic alternation (no filters)
|
// Splitter — basic alternation (no filters)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1000,7 +1082,34 @@ TEST_CASE("BeltSystem: deconstruct entry discards transit items", "[belt]")
|
|||||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BeltSystem: clearTiles discards tunnel transit items", "[belt]")
|
// A clear removes exactly what countItems reports for the tile it acts on
|
||||||
|
// (REQ-UI-BELT-CLEAR, REQ-UI-BELT-ITEMS): the transit items belong to the exit, so only
|
||||||
|
// the exit's clear discards them.
|
||||||
|
TEST_CASE("BeltSystem: clearTiles on a tunnel exit discards transit items", "[belt]")
|
||||||
|
{
|
||||||
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
|
||||||
|
const QPoint entry(0, 0);
|
||||||
|
const QPoint exit(5, 0);
|
||||||
|
|
||||||
|
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||||
|
bs.placeTunnelExit(exit, Rotation::East);
|
||||||
|
|
||||||
|
bs.tryPutItem(entry, makeItem("iron_ore"), Rotation::East);
|
||||||
|
bs.tick();
|
||||||
|
bs.tick();
|
||||||
|
|
||||||
|
bs.clearTiles({exit});
|
||||||
|
|
||||||
|
for (int i = 0; i < 30; ++i)
|
||||||
|
{
|
||||||
|
bs.tick();
|
||||||
|
}
|
||||||
|
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BeltSystem: clearTiles on a tunnel entry leaves transit items travelling",
|
||||||
|
"[belt]")
|
||||||
{
|
{
|
||||||
BeltSystem bs(kFastBeltSpeed);
|
BeltSystem bs(kFastBeltSpeed);
|
||||||
|
|
||||||
@@ -1020,7 +1129,7 @@ TEST_CASE("BeltSystem: clearTiles discards tunnel transit items", "[belt]")
|
|||||||
{
|
{
|
||||||
bs.tick();
|
bs.tick();
|
||||||
}
|
}
|
||||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
REQUIRE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BeltSystem: belt to entry to transit to exit to belt full chain", "[belt]")
|
TEST_CASE("BeltSystem: belt to entry to transit to exit to belt full chain", "[belt]")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "BeltItemList.h"
|
||||||
#include "BuildingTarget.h"
|
#include "BuildingTarget.h"
|
||||||
#include "ClearBeltControl.h"
|
#include "ClearBeltControl.h"
|
||||||
#include "SelectionNames.h"
|
#include "SelectionNames.h"
|
||||||
@@ -17,6 +18,10 @@ BeltContent::BeltContent(const SelectionContext& context,
|
|||||||
parent)
|
parent)
|
||||||
, m_ids(request.buildings)
|
, m_ids(request.buildings)
|
||||||
{
|
{
|
||||||
|
// What is there, then the button that removes it (REQ-UI-BELT-ITEMS,
|
||||||
|
// REQ-UI-BELT-CLEAR).
|
||||||
|
m_itemList = new BeltItemList(context, m_ids, this);
|
||||||
|
getRuntimeLayout()->addWidget(m_itemList);
|
||||||
getRuntimeLayout()->addWidget(new ClearBeltControl(context, m_ids, this));
|
getRuntimeLayout()->addWidget(new ClearBeltControl(context, m_ids, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,11 +33,17 @@ void BeltContent::refreshConfiguration()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An aggregated selection may mix belts with tunnel ends, so it is named after the
|
// An aggregated selection may mix belts with tunnel ends and splitters, so it is
|
||||||
// first tile; the count says how many are held (REQ-UI-SELECTION-AGGREGATE).
|
// named after the first tile; the count says how many are held
|
||||||
|
// (REQ-UI-SELECTION-AGGREGATE).
|
||||||
setBuildingIdentity(target.type, getBuildingTypeName(target.type));
|
setBuildingIdentity(target.type, getBuildingTypeName(target.type));
|
||||||
if (m_ids.size() > 1)
|
if (m_ids.size() > 1)
|
||||||
{
|
{
|
||||||
setCountSlot(static_cast<int>(m_ids.size()));
|
setCountSlot(static_cast<int>(m_ids.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BeltContent::refreshRuntime()
|
||||||
|
{
|
||||||
|
m_itemList->refresh();
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,13 +6,17 @@
|
|||||||
#include "SelectionContent.h"
|
#include "SelectionContent.h"
|
||||||
#include "SelectionContentFactory.h"
|
#include "SelectionContentFactory.h"
|
||||||
|
|
||||||
// The card for a belt, a tunnel entry or a tunnel exit
|
class BeltItemList;
|
||||||
// (REQ-UI-SELECTION-CONTENT). These carry no configuration and no buffers of their own;
|
|
||||||
// their card is the clear action alone (REQ-UI-BELT-CLEAR).
|
// The card for a belt, a tunnel entry or a tunnel exit (REQ-UI-SELECTION-CONTENT). These
|
||||||
|
// carry no configuration and no buffers of their own; their card is what the tiles are
|
||||||
|
// carrying with the clear action beneath it (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
|
||||||
//
|
//
|
||||||
// Because that action already operates on the whole selection, several of them aggregate
|
// Because both parts read and act on the whole selection, any mix of belt-subsystem tiles
|
||||||
// into this one card with the count in the header (REQ-UI-SELECTION-AGGREGATE) -- the
|
// aggregates into this one card with the count in the header
|
||||||
// splitter is not among them, as its output filters are per-object.
|
// (REQ-UI-SELECTION-AGGREGATE) -- splitters among them, whose per-object output filters
|
||||||
|
// are simply not shown for an aggregate; a lone splitter gets its own card instead
|
||||||
|
// (SplitterContent).
|
||||||
class BeltContent : public SelectionContent
|
class BeltContent : public SelectionContent
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -23,8 +27,9 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void refreshConfiguration() override;
|
void refreshConfiguration() override;
|
||||||
void refreshRuntime() override {}
|
void refreshRuntime() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<BuildingId> m_ids;
|
std::vector<BuildingId> m_ids;
|
||||||
|
BeltItemList* m_itemList;
|
||||||
};
|
};
|
||||||
|
|||||||
63
src/ui/selection/BeltItemList.cpp
Normal file
63
src/ui/selection/BeltItemList.cpp
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "BeltItemList.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "BeltSystem.h"
|
||||||
|
#include "DisplayName.h"
|
||||||
|
#include "EmptyNote.h"
|
||||||
|
#include "FactoryQueries.h"
|
||||||
|
#include "ItemChipRow.h"
|
||||||
|
#include "ItemType.h"
|
||||||
|
#include "SectionBox.h"
|
||||||
|
#include "Simulation.h"
|
||||||
|
|
||||||
|
BeltItemList::BeltItemList(const SelectionContext& context,
|
||||||
|
const std::vector<BuildingId>& ids, QWidget* parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_context(context)
|
||||||
|
, m_ids(ids)
|
||||||
|
{
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->setSpacing(0);
|
||||||
|
|
||||||
|
m_section = new SectionBox(tr("Items"), this);
|
||||||
|
m_chips = new ItemChipRow(m_context, m_section);
|
||||||
|
m_emptyNote = new EmptyNote(tr("No items"), m_section);
|
||||||
|
m_section->getContentLayout()->addWidget(m_chips);
|
||||||
|
m_section->getContentLayout()->addWidget(m_emptyNote);
|
||||||
|
layout->addWidget(m_section);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeltItemList::refresh()
|
||||||
|
{
|
||||||
|
// Both the list and the clear action derive their tiles the same way, so what is
|
||||||
|
// listed is what the button removes (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
|
||||||
|
const std::vector<QPoint> tiles =
|
||||||
|
collectBeltTiles(m_context.sim->getFactoryState(), m_ids);
|
||||||
|
const std::map<ItemType, int> counts = m_context.sim->getBelts().countItems(tiles);
|
||||||
|
|
||||||
|
// Keyed by item type, so the chips come out ordered by item id and a chip does not
|
||||||
|
// move as its count changes (REQ-UI-BELT-ITEMS).
|
||||||
|
std::vector<ItemChipRow::Entry> entries;
|
||||||
|
entries.reserve(counts.size());
|
||||||
|
for (const std::pair<const ItemType, int>& count : counts)
|
||||||
|
{
|
||||||
|
ItemChipRow::Entry entry;
|
||||||
|
entry.itemId = count.first.id;
|
||||||
|
entry.countText = QString::number(count.second);
|
||||||
|
entry.subLine = QString::fromStdString(toDisplayName(count.first.id));
|
||||||
|
entries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A belt has no fixed set of item types it can hold, unlike a building's buffers, so
|
||||||
|
// there is no chip to show as `0` and the note takes the section's place instead.
|
||||||
|
m_chips->setEntries(entries);
|
||||||
|
m_emptyNote->setVisible(entries.empty());
|
||||||
|
}
|
||||||
45
src/ui/selection/BeltItemList.h
Normal file
45
src/ui/selection/BeltItemList.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "BuildingId.h"
|
||||||
|
#include "SelectionContext.h"
|
||||||
|
|
||||||
|
class EmptyNote;
|
||||||
|
class ItemChipRow;
|
||||||
|
class SectionBox;
|
||||||
|
|
||||||
|
// What the selected belt-subsystem tiles are carrying (REQ-UI-BELT-ITEMS): one item chip
|
||||||
|
// per item type held across the whole selection, in the same form the buffer sections and
|
||||||
|
// the HQ's block stock use (REQ-UI-SINGLE-SELECTION, REQ-UI-HQ-PANEL).
|
||||||
|
//
|
||||||
|
// It stands above the clear action so the player reads a line's contents before emptying
|
||||||
|
// it, and it is the only place those contents can be read at all -- items on a moving belt
|
||||||
|
// are too small and too transient to count by eye, and items inside a tunnel are drawn
|
||||||
|
// nowhere (REQ-BLD-TUNNEL-TRANSIT).
|
||||||
|
//
|
||||||
|
// Shared by the belt card and the splitter card, which show the same thing over different
|
||||||
|
// selections (REQ-UI-SELECTION-AGGREGATE).
|
||||||
|
class BeltItemList : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
BeltItemList(const SelectionContext& context, const std::vector<BuildingId>& ids,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
// Re-reads what the tiles hold. Called per refresh of the owning card's runtime
|
||||||
|
// group, the counts changing as often as items move.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SelectionContext m_context;
|
||||||
|
std::vector<BuildingId> m_ids;
|
||||||
|
SectionBox* m_section;
|
||||||
|
ItemChipRow* m_chips;
|
||||||
|
// Shown in the chips' place while the tiles carry nothing, so the card holds one
|
||||||
|
// shape as items come and go (REQ-UI-BELT-ITEMS).
|
||||||
|
EmptyNote* m_emptyNote;
|
||||||
|
};
|
||||||
@@ -17,6 +17,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.h
|
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BeltItemList.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BufferedBuildingContent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BufferedBuildingContent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeProductionContent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/RecipeProductionContent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipyardContent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ShipyardContent.h
|
||||||
@@ -50,6 +51,7 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/ProductionSection.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RecipeSelectionControl.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/ClearBeltControl.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/BeltItemList.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BufferedBuildingContent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BufferedBuildingContent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/RecipeProductionContent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/RecipeProductionContent.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ShipyardContent.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/ShipyardContent.cpp
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "Building.h"
|
|
||||||
#include "Command.h"
|
#include "Command.h"
|
||||||
#include "CommandRequestedEvent.h"
|
#include "CommandRequestedEvent.h"
|
||||||
#include "EventManager.h"
|
#include "EventManager.h"
|
||||||
@@ -21,7 +20,7 @@ ClearBeltControl::ClearBeltControl(const SelectionContext& context,
|
|||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
layout->setSpacing(0);
|
layout->setSpacing(0);
|
||||||
|
|
||||||
QPushButton* button = new QPushButton(tr("Clear stuck items"), this);
|
QPushButton* button = new QPushButton(tr("Clear items"), this);
|
||||||
layout->addWidget(button);
|
layout->addWidget(button);
|
||||||
|
|
||||||
connect(button, &QPushButton::clicked, this, [this]() { clearSelectedTiles(); });
|
connect(button, &QPushButton::clicked, this, [this]() { clearSelectedTiles(); });
|
||||||
@@ -29,18 +28,10 @@ ClearBeltControl::ClearBeltControl(const SelectionContext& context,
|
|||||||
|
|
||||||
void ClearBeltControl::clearSelectedTiles() const
|
void ClearBeltControl::clearSelectedTiles() const
|
||||||
{
|
{
|
||||||
std::vector<QPoint> tiles;
|
// The same tiles the item list above states the contents of (REQ-UI-BELT-ITEMS), so
|
||||||
for (BuildingId id : m_ids)
|
// the button cannot act on more or less than what the player was shown.
|
||||||
{
|
const std::vector<QPoint> tiles =
|
||||||
const Building* building = findBuilding(m_context.sim->getFactoryState(), id);
|
collectBeltTiles(m_context.sim->getFactoryState(), m_ids);
|
||||||
if (building && isBeltSubsystemType(building->type))
|
|
||||||
{
|
|
||||||
for (const QPoint& cell : building->bodyCells)
|
|
||||||
{
|
|
||||||
tiles.push_back(cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tiles.empty())
|
if (tiles.empty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -48,7 +39,7 @@ void ClearBeltControl::clearSelectedTiles() const
|
|||||||
|
|
||||||
std::shared_ptr<ClearBeltTilesCommand> command =
|
std::shared_ptr<ClearBeltTilesCommand> command =
|
||||||
std::make_shared<ClearBeltTilesCommand>();
|
std::make_shared<ClearBeltTilesCommand>();
|
||||||
command->tiles = std::move(tiles);
|
command->tiles = tiles;
|
||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<CommandRequestedEvent>(command));
|
std::make_shared<CommandRequestedEvent>(command));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,20 @@
|
|||||||
#include "BuildingId.h"
|
#include "BuildingId.h"
|
||||||
#include "SelectionContext.h"
|
#include "SelectionContext.h"
|
||||||
|
|
||||||
// The "Clear stuck items" action of the card's runtime group (REQ-UI-BELT-CLEAR): it
|
// The "Clear items" action of the card's runtime group (REQ-UI-BELT-CLEAR): it removes
|
||||||
// removes every item from the selected belt, splitter and tunnel tiles, which is how a
|
// every item from the selected belt, splitter and tunnel tiles -- how a stalled line is
|
||||||
// stalled line is resolved.
|
// resolved, and equally how a running one is emptied, which is why the button is named
|
||||||
|
// for what it does rather than for a state the items need not be in.
|
||||||
//
|
//
|
||||||
// It acts on the whole selection rather than on one tile, which is why a selection of
|
// It stands below the item list and removes exactly what that list shows
|
||||||
// belts and tunnel ends aggregates into a single card instead of a count summary
|
// (REQ-UI-BELT-ITEMS): a tunnel exit takes its tunnel's invisible cargo with it, a tunnel
|
||||||
// (REQ-UI-SELECTION-AGGREGATE), and why the count summary shows the action too when a
|
// entry does not.
|
||||||
// belt is among the selected buildings.
|
//
|
||||||
|
// It acts on the whole selection rather than on one tile, which is why any mix of
|
||||||
|
// belt-subsystem tiles aggregates into a single card instead of a count summary
|
||||||
|
// (REQ-UI-SELECTION-AGGREGATE). It is shown nowhere else: a selection mixing belt tiles
|
||||||
|
// with other buildings shows the count summary and carries neither the list nor this
|
||||||
|
// action, a button acting on part of a selection being worse than no button.
|
||||||
class ClearBeltControl : public QWidget
|
class ClearBeltControl : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "Building.h"
|
#include "Building.h"
|
||||||
#include "BuildingIconCache.h"
|
#include "BuildingIconCache.h"
|
||||||
#include "ClearBeltControl.h"
|
|
||||||
#include "CountRow.h"
|
#include "CountRow.h"
|
||||||
#include "FactoryQueries.h"
|
#include "FactoryQueries.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
@@ -38,22 +37,11 @@ MultiBuildingContent::MultiBuildingContent(const SelectionContext& context,
|
|||||||
|
|
||||||
buildSummary();
|
buildSummary();
|
||||||
|
|
||||||
// A selection holding any belt-subsystem tile can still be cleared as a whole
|
// No item list and no clear action here, even when the selection holds belt tiles:
|
||||||
// (REQ-UI-BELT-CLEAR), even though the mixture is what kept it from aggregating.
|
// both speak of belt-subsystem tiles alone, and a button acting on part of a
|
||||||
bool hasBeltTile = false;
|
// selection would be worse than no button (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
|
||||||
for (BuildingId id : m_ids)
|
// Selecting the tiles by themselves aggregates them into the belt card instead
|
||||||
{
|
// (REQ-UI-SELECTION-AGGREGATE).
|
||||||
const Building* building = findBuilding(context.sim->getFactoryState(), id);
|
|
||||||
if (building && isBeltSubsystemType(building->type))
|
|
||||||
{
|
|
||||||
hasBeltTile = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasBeltTile)
|
|
||||||
{
|
|
||||||
getRuntimeLayout()->addWidget(new ClearBeltControl(context, m_ids, this));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiBuildingContent::buildSummary()
|
void MultiBuildingContent::buildSummary()
|
||||||
|
|||||||
@@ -51,16 +51,6 @@ SelectionContentKind getKindForType(BuildingType type)
|
|||||||
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)
|
ContentKey chooseBuildingContent(const SelectionRequest& request, Simulation& sim)
|
||||||
{
|
{
|
||||||
const FactoryState& state = sim.getFactoryState();
|
const FactoryState& state = sim.getFactoryState();
|
||||||
@@ -83,21 +73,25 @@ ContentKey chooseBuildingContent(const SelectionRequest& request, Simulation& si
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Several buildings aggregate into one card only when every part of that card
|
// Several buildings aggregate into one card only when every part of that card's
|
||||||
// aggregates (REQ-UI-SELECTION-AGGREGATE). Construction sites are excluded from the
|
// runtime content aggregates (REQ-UI-SELECTION-AGGREGATE). Belt-subsystem tiles do:
|
||||||
// belt case: a site's card is its own construction progress, which several sites
|
// the item list and the clear action already read and act on the whole selection
|
||||||
// cannot share.
|
// (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR). Splitters are among them -- their output
|
||||||
bool allAggregatableBelts = true;
|
// filters are per-object configuration, which an aggregate simply does not show.
|
||||||
|
//
|
||||||
|
// Construction sites are excluded: findBuilding fails for a site, whose card is its
|
||||||
|
// own construction progress and whose tile the belt subsystem does not know yet.
|
||||||
|
bool allBeltTiles = true;
|
||||||
for (BuildingId id : request.buildings)
|
for (BuildingId id : request.buildings)
|
||||||
{
|
{
|
||||||
const Building* building = findBuilding(state, id);
|
const Building* building = findBuilding(state, id);
|
||||||
if (!building || !isAggregatableBeltType(building->type))
|
if (!building || !isBeltSubsystemType(building->type))
|
||||||
{
|
{
|
||||||
allAggregatableBelts = false;
|
allBeltTiles = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allAggregatableBelts)
|
if (allBeltTiles)
|
||||||
{
|
{
|
||||||
return { SelectionContentKind::Belt, false };
|
return { SelectionContentKind::Belt, false };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "BeltItemList.h"
|
||||||
#include "BeltSystem.h"
|
#include "BeltSystem.h"
|
||||||
#include "BuildingTarget.h"
|
#include "BuildingTarget.h"
|
||||||
#include "ClearBeltControl.h"
|
#include "ClearBeltControl.h"
|
||||||
@@ -102,6 +103,10 @@ SplitterContent::SplitterContent(const SelectionContext& context,
|
|||||||
getConfigurationLayout()->addWidget(m_filterBLabel);
|
getConfigurationLayout()->addWidget(m_filterBLabel);
|
||||||
getConfigurationLayout()->addWidget(m_filterBList);
|
getConfigurationLayout()->addWidget(m_filterBList);
|
||||||
|
|
||||||
|
// What the splitter is holding, then the button that removes it (REQ-UI-BELT-ITEMS,
|
||||||
|
// REQ-UI-BELT-CLEAR) -- the same pair the belt card shows, over one tile.
|
||||||
|
m_itemList = new BeltItemList(context, request.buildings, this);
|
||||||
|
getRuntimeLayout()->addWidget(m_itemList);
|
||||||
getRuntimeLayout()->addWidget(
|
getRuntimeLayout()->addWidget(
|
||||||
new ClearBeltControl(context, request.buildings, this));
|
new ClearBeltControl(context, request.buildings, this));
|
||||||
|
|
||||||
@@ -125,6 +130,11 @@ void SplitterContent::refreshConfiguration()
|
|||||||
setBuildingIdentity(target.type, getBuildingTypeName(target.type));
|
setBuildingIdentity(target.type, getBuildingTypeName(target.type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SplitterContent::refreshRuntime()
|
||||||
|
{
|
||||||
|
m_itemList->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
void SplitterContent::populateFilters()
|
void SplitterContent::populateFilters()
|
||||||
{
|
{
|
||||||
const BuildingTarget target = resolveBuildingTarget(getContext(), m_id);
|
const BuildingTarget target = resolveBuildingTarget(getContext(), m_id);
|
||||||
|
|||||||
@@ -6,16 +6,22 @@
|
|||||||
#include "SelectionContent.h"
|
#include "SelectionContent.h"
|
||||||
#include "SelectionContentFactory.h"
|
#include "SelectionContentFactory.h"
|
||||||
|
|
||||||
|
class BeltItemList;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
|
|
||||||
// The card for a Splitter (REQ-UI-SELECTION-CONTENT): its two per-output item filters
|
// The card for a single selected Splitter (REQ-UI-SELECTION-CONTENT): its two per-output
|
||||||
// (REQ-BLD-SPLITTER) plus the clear action every belt-subsystem tile has
|
// item filters (REQ-BLD-SPLITTER), then what it is holding and the clear action every
|
||||||
// (REQ-UI-BELT-CLEAR).
|
// belt-subsystem tile has (REQ-UI-BELT-ITEMS, REQ-UI-BELT-CLEAR).
|
||||||
|
//
|
||||||
|
// Only for a splitter selected on its own. The filters are per-object, so a selection
|
||||||
|
// holding a splitter among other belt-subsystem tiles aggregates into BeltContent and
|
||||||
|
// shows none (REQ-UI-SELECTION-AGGREGATE).
|
||||||
//
|
//
|
||||||
// The filters are configuration, so they are shown for a construction site too and set
|
// The filters are configuration, so they are shown for a construction site too and set
|
||||||
// through the site's own command (REQ-BLD-SITE-CONFIG); the clear action is runtime and
|
// through the site's own command (REQ-BLD-SITE-CONFIG); the item list and the clear
|
||||||
// therefore is not, because a site's tile is not registered with the belt subsystem yet.
|
// action are runtime and therefore are not, because a site's tile is not registered with
|
||||||
|
// the belt subsystem yet.
|
||||||
class SplitterContent : public SelectionContent
|
class SplitterContent : public SelectionContent
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -26,7 +32,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void refreshConfiguration() override;
|
void refreshConfiguration() override;
|
||||||
void refreshRuntime() override {}
|
void refreshRuntime() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Sends the checked items of both lists as the splitter's new filters. Routed to the
|
// Sends the checked items of both lists as the splitter's new filters. Routed to the
|
||||||
@@ -37,11 +43,12 @@ private:
|
|||||||
// the player is in the middle of clicking.
|
// the player is in the middle of clicking.
|
||||||
void populateFilters();
|
void populateFilters();
|
||||||
|
|
||||||
BuildingId m_id;
|
BuildingId m_id;
|
||||||
bool m_isSite;
|
bool m_isSite;
|
||||||
QPoint m_tile;
|
QPoint m_tile;
|
||||||
QLabel* m_filterALabel;
|
BeltItemList* m_itemList;
|
||||||
QListWidget* m_filterAList;
|
QLabel* m_filterALabel;
|
||||||
QLabel* m_filterBLabel;
|
QListWidget* m_filterAList;
|
||||||
QListWidget* m_filterBList;
|
QLabel* m_filterBLabel;
|
||||||
|
QListWidget* m_filterBList;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user