Animate items emerging from building output ports
This commit is contained in:
@@ -20,6 +20,21 @@ bool isAutoRecipeBuildingType(BuildingType type)
|
||||
return type == BuildingType::Smelter
|
||||
|| type == BuildingType::ReprocessingPlant;
|
||||
}
|
||||
|
||||
// The building body tile that owns an output port, given the port's outside tile
|
||||
// (port.tile) and its facing direction. The virtual output belt occupies this tile
|
||||
// and flows toward port.tile (REQ-MAT-OUTPUT-EMERGE).
|
||||
QPoint outputBodyTile(QPoint portTile, Rotation direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case Rotation::East: return portTile + QPoint(-1, 0);
|
||||
case Rotation::West: return portTile + QPoint( 1, 0);
|
||||
case Rotation::North: return portTile + QPoint( 0, 1);
|
||||
case Rotation::South: return portTile + QPoint( 0, -1);
|
||||
}
|
||||
return portTile;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||
@@ -523,6 +538,9 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
|
||||
building.inputBuffer.caps.clear();
|
||||
building.outputBuffer.items.clear();
|
||||
building.outputBuffer.capacity = 0;
|
||||
// Emerging items are part of the output buffer, so clearing it on a
|
||||
// recipe change discards them too (REQ-MAT-OUTPUT-EMERGE).
|
||||
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
||||
building.production = std::nullopt;
|
||||
|
||||
if (!recipeId.empty())
|
||||
@@ -569,6 +587,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
|
||||
building.inputBuffer.caps.clear();
|
||||
building.outputBuffer.items.clear();
|
||||
building.outputBuffer.capacity = 0;
|
||||
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
||||
if (!building.recipeId.empty() && building.type == BuildingType::Shipyard)
|
||||
{
|
||||
initShipyardBuffers(building);
|
||||
@@ -672,6 +691,7 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
||||
absPort.direction = port.direction;
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building);
|
||||
|
||||
if (building.type == BuildingType::SalvageBay)
|
||||
@@ -929,8 +949,9 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Output buffer has space for chosen outputs?
|
||||
const int newSize = static_cast<int>(building.outputBuffer.items.size())
|
||||
// 3. Output buffer has space for chosen outputs? Emerging items still
|
||||
// count against the buffer (REQ-MAT-OUTPUT-EMERGE).
|
||||
const int newSize = building.outputItemCount()
|
||||
+ static_cast<int>(chosen.size());
|
||||
if (newSize > building.outputBuffer.capacity)
|
||||
{
|
||||
@@ -1064,31 +1085,69 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
||||
}
|
||||
}
|
||||
|
||||
void BuildingSystem::tickBeltPush()
|
||||
void BuildingSystem::tickOutputBelts()
|
||||
{
|
||||
TRACE();
|
||||
// Use BeltSystem's own per-tick step so emerging items travel at exactly the
|
||||
// same speed as real belts (REQ-GW-BELT-SPEED, REQ-MAT-OUTPUT-EMERGE).
|
||||
const double progressPerTick = m_belts.getProgressPerTick_tpt();
|
||||
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
if (building.outputBuffer.items.empty())
|
||||
for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const Port& port = building.outputPorts[p];
|
||||
std::vector<BeltItemSlot>& lane = building.emergingItems[p];
|
||||
|
||||
for (const Port& outputPort : building.outputPorts)
|
||||
{
|
||||
if (building.outputBuffer.items.empty())
|
||||
// 1. Advance emerging items using the shared belt packing (progress
|
||||
// caps to 0.5 / 0.75 / 1.0 for up to three items).
|
||||
advanceBeltSlots(lane, progressPerTick);
|
||||
|
||||
// 2. Hand the front item off onto the adjacent real belt once it reaches
|
||||
// the output edge (progress 1.0). On refusal — no belt, output-edge
|
||||
// (REQ-MAT-ACCEPT-DIR), or a full belt — it stays stuck at 1.0.
|
||||
if (!lane.empty() && lane.front().progress >= 1.0
|
||||
&& m_belts.tryPutItem(port.tile, lane.front().item, port.direction))
|
||||
{
|
||||
break;
|
||||
lane.erase(lane.begin());
|
||||
}
|
||||
const Item item = building.outputBuffer.items.front();
|
||||
if (m_belts.tryPutItem(outputPort.tile, item, outputPort.direction))
|
||||
|
||||
// 3. Feed the next buffered item onto the lane at progress 0.5 when the
|
||||
// entry slot is free — the lane holds at most three items and a new
|
||||
// one needs a quarter-tile clearance ahead of 0.5.
|
||||
if (!building.outputBuffer.items.empty()
|
||||
&& lane.size() < 3
|
||||
&& (lane.empty() || lane.back().progress >= 0.75))
|
||||
{
|
||||
lane.push_back(BeltItemSlot{building.outputBuffer.items.front(), 0.5});
|
||||
building.outputBuffer.items.erase(building.outputBuffer.items.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BuildingSystem::forEachEmergingItem(
|
||||
const std::function<void(const ItemType&, QPointF)>& visit) const
|
||||
{
|
||||
for (const Building& building : m_buildings)
|
||||
{
|
||||
for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
|
||||
{
|
||||
const Port& port = building.outputPorts[p];
|
||||
const QPoint bodyTile = outputBodyTile(port.tile, port.direction);
|
||||
const std::vector<BeltItemSlot>& lane = building.emergingItems[p];
|
||||
|
||||
// Render least-progressed first (bottom) → most-progressed last (top),
|
||||
// matching belt item ordering (REQ-GW-TILE-SIZE).
|
||||
for (int i = static_cast<int>(lane.size()) - 1; i >= 0; --i)
|
||||
{
|
||||
visit(lane[i].item.type,
|
||||
beltSlotWorldPos(bodyTile, port.direction, lane[i].progress));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1278,6 +1337,10 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
|
||||
absPort.direction = port.direction;
|
||||
b.outputPorts.push_back(absPort);
|
||||
}
|
||||
// The output ports moved; discard any in-flight emerging items and re-size
|
||||
// the lanes to the new port set (REQ-MAT-OUTPUT-EMERGE).
|
||||
b.emergingItems.clear();
|
||||
b.emergingItems.resize(b.outputPorts.size());
|
||||
b.inputPorts = computeInputPorts(b);
|
||||
|
||||
// Re-register with BeltSystem (items on tile are discarded).
|
||||
@@ -1347,7 +1410,9 @@ bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (static_cast<int>(bay->outputBuffer.items.size()) >= bay->outputBuffer.capacity)
|
||||
// Emerging scrap still counts against the bay's holding capacity
|
||||
// (REQ-MAT-OUTPUT-EMERGE).
|
||||
if (bay->outputItemCount() >= bay->outputBuffer.capacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1382,6 +1447,7 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
|
||||
absPort.direction = port.direction;
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building);
|
||||
|
||||
if (type == BuildingType::SalvageBay)
|
||||
@@ -1488,6 +1554,16 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
|
||||
appendInputBuffer(hasher, b.inputBuffer);
|
||||
appendItems(hasher, b.outputBuffer.items);
|
||||
hasher.append(b.outputBuffer.capacity);
|
||||
hasher.append(b.emergingItems.size());
|
||||
for (const std::vector<BeltItemSlot>& lane : b.emergingItems)
|
||||
{
|
||||
hasher.append(lane.size());
|
||||
for (const BeltItemSlot& slot : lane)
|
||||
{
|
||||
hasher.append(slot.item.type.id);
|
||||
hasher.append(slot.progress);
|
||||
}
|
||||
}
|
||||
hasher.append(b.production.has_value());
|
||||
if (b.production.has_value())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user