dont require output belts to be aligned with output ports
This commit is contained in:
@@ -82,7 +82,7 @@ Output port indicators are not building tiles themselves. A building may have mo
|
||||
|
||||
- REQ-MAT-BELT-ONLY: Materials are transported exclusively via belts and splitters.
|
||||
- REQ-MAT-INPUT-PORTS: A building accepts items from any adjacent belt tile on any edge of its footprint (excluding cells occupied by output port(s)) whose direction points toward the building, provided the item is an input required by the currently selected recipe and the matching per-material input buffer has free space.
|
||||
- REQ-MAT-OUTPUT-PORT: Each building has one or more fixed output port(s) defined by its surface_mask (direction determined by rotation). Produced items are placed onto the belt at the output port.
|
||||
- REQ-MAT-OUTPUT-PORT: Each building has one or more fixed output port(s) defined by its surface_mask (direction determined by rotation). Produced items are placed onto the belt at the output port tile regardless of that belt's direction.
|
||||
- REQ-MAT-INPUT-BUFFER: Each building has one input buffer per required input material. Each per-material buffer holds up to twice that material's per-cycle requirement. When the player selects a new recipe or blueprint, all items in all input buffers are cleared.
|
||||
- REQ-MAT-OUTPUT-BUFFER: Each building has an output buffer that holds up to twice the quantity produced by one production cycle. If the output buffer is full, production stops until space is available. When the player selects a new recipe or blueprint, all items in the output buffer are cleared (relevant when the adjacent belt is jammed and items have accumulated).
|
||||
- REQ-MAT-OUTPUT-BUFFER-REPROCESSING: Exception to REQ-MAT-OUTPUT-BUFFER — the Reprocessing Plant's output buffer holds at most one cycle's output. This prevents exploits where the player stalls the output belt to force the plant to reroll.
|
||||
|
||||
@@ -92,18 +92,14 @@ void BeltSystem::setSplitterFilters(QPoint tile,
|
||||
// Port interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool BeltSystem::tryPutItem(Port port, Item item)
|
||||
bool BeltSystem::tryPutItem(QPoint tile, Item item)
|
||||
{
|
||||
const std::map<std::pair<int, int>, BeltTile>::iterator it = m_belts.find(key(port.tile));
|
||||
const std::map<std::pair<int, int>, BeltTile>::iterator it = m_belts.find(key(tile));
|
||||
if (it == m_belts.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (it->second.direction != port.direction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return tryPlaceOnBelt(port.tile, item);
|
||||
return tryPlaceOnBelt(tile, item);
|
||||
}
|
||||
|
||||
std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
||||
|
||||
@@ -56,9 +56,9 @@ public:
|
||||
// port.tile = the belt tile adjacent to the building
|
||||
// port.direction = direction items flow on that tile
|
||||
//
|
||||
// tryPutItem: place item onto port.tile entering from the opposite side.
|
||||
// Returns false if the tile is not a belt, direction mismatches, or tile full.
|
||||
bool tryPutItem(Port port, Item item);
|
||||
// tryPutItem: place item onto tile.
|
||||
// Returns false if the tile is not a belt, or tile full.
|
||||
bool tryPutItem(QPoint tile, Item item);
|
||||
|
||||
// tryTakeItem: remove and return the leading item from port.tile.
|
||||
// Returns nullopt if tile is not a belt, direction mismatches, or tile empty.
|
||||
@@ -119,3 +119,4 @@ private:
|
||||
std::map<std::pair<int, int>, BeltTile> m_belts;
|
||||
std::map<std::pair<int, int>, SplitterTile> m_splitters;
|
||||
};
|
||||
|
||||
|
||||
@@ -726,7 +726,7 @@ void BuildingSystem::tickBeltPush()
|
||||
break;
|
||||
}
|
||||
const Item item = building.outputBuffer.items.front();
|
||||
if (m_belts.tryPutItem(outputPort, item))
|
||||
if (m_belts.tryPutItem(outputPort.tile, item))
|
||||
{
|
||||
building.outputBuffer.items.erase(building.outputBuffer.items.begin());
|
||||
}
|
||||
|
||||
@@ -36,29 +36,20 @@ static Port eastPort(QPoint tile)
|
||||
// Placement
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BeltSystem: tryPutItem succeeds on registered belt with matching direction", "[belt]")
|
||||
TEST_CASE("BeltSystem: tryPutItem succeeds on registered belt", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
|
||||
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
|
||||
REQUIRE(bs.tryPutItem(tile, makeItem("iron_ore")));
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tryPutItem fails on unregistered tile", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
REQUIRE_FALSE(bs.tryPutItem(eastPort(QPoint(0, 0)), makeItem("iron_ore")));
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tryPutItem fails on direction mismatch", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::North);
|
||||
|
||||
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
|
||||
REQUIRE_FALSE(bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore")));
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tryPutItem fails after removeTile", "[belt]")
|
||||
@@ -68,7 +59,7 @@ TEST_CASE("BeltSystem: tryPutItem fails after removeTile", "[belt]")
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.removeTile(tile);
|
||||
|
||||
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
|
||||
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("iron_ore")));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -81,8 +72,8 @@ TEST_CASE("BeltSystem: two items fit in one tile", "[belt]")
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
|
||||
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
|
||||
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("copper_ore")));
|
||||
REQUIRE(bs.tryPutItem(tile, makeItem("iron_ore")));
|
||||
REQUIRE(bs.tryPutItem(tile, makeItem("copper_ore")));
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
|
||||
@@ -91,10 +82,10 @@ TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
|
||||
bs.tryPutItem(eastPort(tile), makeItem("a"));
|
||||
bs.tryPutItem(eastPort(tile), makeItem("b"));
|
||||
bs.tryPutItem(tile, makeItem("a"));
|
||||
bs.tryPutItem(tile, makeItem("b"));
|
||||
|
||||
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("c")));
|
||||
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("c")));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -106,7 +97,7 @@ TEST_CASE("BeltSystem: tryTakeItem returns placed item after reaching output edg
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tile, makeItem("iron_ore"));
|
||||
bs.tick(); // advance to output edge
|
||||
|
||||
const std::optional<Item> taken = bs.tryTakeItem(eastPort(tile));
|
||||
@@ -120,7 +111,7 @@ TEST_CASE("BeltSystem: tryTakeItem requires item to reach output edge before yie
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tile, makeItem("iron_ore"));
|
||||
|
||||
// Item placed but not yet at output edge — must not be available.
|
||||
REQUIRE_FALSE(bs.tryTakeItem(eastPort(tile)).has_value());
|
||||
@@ -136,8 +127,8 @@ TEST_CASE("BeltSystem: tryTakeItem with two items returns both after each reache
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("first"));
|
||||
bs.tryPutItem(eastPort(tile), makeItem("second"));
|
||||
bs.tryPutItem(tile, makeItem("first"));
|
||||
bs.tryPutItem(tile, makeItem("second"));
|
||||
|
||||
// Front item reaches output edge after one tick.
|
||||
bs.tick();
|
||||
@@ -160,16 +151,6 @@ TEST_CASE("BeltSystem: tryTakeItem returns nullopt on empty tile", "[belt]")
|
||||
REQUIRE_FALSE(bs.tryTakeItem(eastPort(QPoint(0, 0))).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tryTakeItem returns nullopt on direction mismatch", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::North);
|
||||
bs.tryPutItem(Port{tile, Rotation::North}, makeItem("x"));
|
||||
|
||||
REQUIRE_FALSE(bs.tryTakeItem(eastPort(tile)).has_value());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tick() — item advancement
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -182,7 +163,7 @@ TEST_CASE("BeltSystem: item transfers from tile A to tile B and becomes availabl
|
||||
bs.placeBelt(tileA, Rotation::East);
|
||||
bs.placeBelt(tileB, Rotation::East);
|
||||
|
||||
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tileA, makeItem("iron_ore"));
|
||||
bs.tick(); // item reaches output edge of A, moves to B at progress 0
|
||||
bs.tick(); // item reaches output edge of B
|
||||
|
||||
@@ -198,7 +179,7 @@ TEST_CASE("BeltSystem: item stays at progress 1.0 when next tile is absent", "[b
|
||||
const QPoint tileA(0, 0);
|
||||
bs.placeBelt(tileA, Rotation::East);
|
||||
|
||||
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tileA, makeItem("iron_ore"));
|
||||
bs.tick();
|
||||
|
||||
// Item should still be on tileA (no registered tile to the east).
|
||||
@@ -215,7 +196,7 @@ TEST_CASE("BeltSystem: item traverses 3-tile chain in 3 ticks (one per tile)", "
|
||||
bs.placeBelt(tileB, Rotation::East);
|
||||
bs.placeBelt(tileC, Rotation::East);
|
||||
|
||||
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tileA, makeItem("iron_ore"));
|
||||
bs.tick(); // A output edge → moves to B at progress 0
|
||||
bs.tick(); // B output edge → moves to C at progress 0
|
||||
bs.tick(); // C output edge → available for pickup
|
||||
@@ -234,11 +215,11 @@ TEST_CASE("BeltSystem: item stays blocked when next tile is full", "[belt]")
|
||||
bs.placeBelt(tileB, Rotation::East);
|
||||
|
||||
// Fill tileB to capacity.
|
||||
bs.tryPutItem(eastPort(tileB), makeItem("b1"));
|
||||
bs.tryPutItem(eastPort(tileB), makeItem("b2"));
|
||||
bs.tryPutItem(tileB, makeItem("b1"));
|
||||
bs.tryPutItem(tileB, makeItem("b2"));
|
||||
|
||||
// Place item in tileA — should be blocked.
|
||||
bs.tryPutItem(eastPort(tileA), makeItem("a1"));
|
||||
bs.tryPutItem(tileA, makeItem("a1"));
|
||||
bs.tick();
|
||||
|
||||
// Item in tileA must still be there.
|
||||
@@ -254,8 +235,8 @@ TEST_CASE("BeltSystem: clearTiles removes all items from specified tiles", "[bel
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
|
||||
bs.tryPutItem(eastPort(tile), makeItem("copper_ore"));
|
||||
bs.tryPutItem(tile, makeItem("iron_ore"));
|
||||
bs.tryPutItem(tile, makeItem("copper_ore"));
|
||||
|
||||
bs.clearTiles({tile});
|
||||
|
||||
@@ -271,7 +252,7 @@ TEST_CASE("BeltSystem: forEachVisualItem visits items inside viewport", "[belt]"
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(5, 5);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tile, makeItem("iron_ore"));
|
||||
|
||||
int count = 0;
|
||||
bs.forEachVisualItem(QRect(0, 0, 20, 20), [&count](VisualItem) { ++count; });
|
||||
@@ -284,7 +265,7 @@ TEST_CASE("BeltSystem: forEachVisualItem skips items outside viewport", "[belt]"
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(50, 50);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tile, makeItem("iron_ore"));
|
||||
|
||||
int count = 0;
|
||||
bs.forEachVisualItem(QRect(0, 0, 20, 20), [&count](VisualItem) { ++count; });
|
||||
@@ -297,7 +278,7 @@ TEST_CASE("BeltSystem: forEachVisualItem reports correct ItemType", "[belt]")
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
bs.tryPutItem(eastPort(tile), makeItem("copper_ingot"));
|
||||
bs.tryPutItem(tile, makeItem("copper_ingot"));
|
||||
|
||||
std::vector<ItemType> seen;
|
||||
bs.forEachVisualItem(QRect(-1, -1, 10, 10), [&seen](VisualItem vi)
|
||||
@@ -329,10 +310,10 @@ TEST_CASE("BeltSystem: splitter alternates between outputA and outputB", "[belt]
|
||||
bs.placeBelt(tileA, Rotation::North);
|
||||
bs.placeBelt(tileB, Rotation::South);
|
||||
|
||||
bs.tryPutItem(eastPort(tileIn), makeItem("item1"));
|
||||
bs.tryPutItem(tileIn, makeItem("item1"));
|
||||
bs.tick(); // item moves: tileIn -> splitter held
|
||||
|
||||
bs.tryPutItem(eastPort(tileIn), makeItem("item2"));
|
||||
bs.tryPutItem(tileIn, makeItem("item2"));
|
||||
bs.tick(); // item1 routes to outputA (North=tileA); item2 moves to splitter
|
||||
|
||||
bs.tick(); // item2 routes to outputB (South=tileB)
|
||||
@@ -366,7 +347,7 @@ TEST_CASE("BeltSystem: splitter routes filtered item to matching output", "[belt
|
||||
// Filter: outputA = iron_ore only; outputB = accept all.
|
||||
bs.setSplitterFilters(tileSpl, {ItemType{"iron_ore"}}, {});
|
||||
|
||||
bs.tryPutItem(eastPort(tileIn), makeItem("iron_ore"));
|
||||
bs.tryPutItem(tileIn, makeItem("iron_ore"));
|
||||
bs.tick(); // tileIn -> splitter held
|
||||
bs.tick(); // routed to outputA (filter match)
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem", "[build
|
||||
|
||||
bs.place(BuildingType::Belt, QPoint(5, 5), Rotation::East, 0);
|
||||
|
||||
REQUIRE(belts.tryPutItem(eastPort(QPoint(5, 5)), makeItem("iron_ore")));
|
||||
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
|
||||
REQUIRE(bs.allBuildings().empty()); // belts do not create Building instances
|
||||
}
|
||||
|
||||
@@ -145,6 +145,12 @@ TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]
|
||||
rng);
|
||||
|
||||
const EntityId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
|
||||
|
||||
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
|
||||
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
|
||||
const int refund = bs.demolish(id);
|
||||
|
||||
// Miner cost = 15, refund = floor(15 * 75 / 100) = 11.
|
||||
@@ -341,7 +347,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
||||
|
||||
// Place west-flowing belt at (2,0): belt flows West, delivers to smelter.
|
||||
belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||
belts.tryPutItem(westPort(QPoint(2, 0)), makeItem("iron_ore"));
|
||||
belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
||||
|
||||
bs.tickBeltPull();
|
||||
|
||||
@@ -493,7 +499,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
||||
belts.placeBelt(QPoint(-1, 0), Rotation::East);
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
belts.tryPutItem(eastPort(QPoint(-1, 0)), makeItem("scrap"));
|
||||
belts.tryPutItem(QPoint(-1, 0), makeItem("scrap"));
|
||||
bs.tickBeltPull();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user