diff --git a/core/src/Portal.cpp b/core/src/Portal.cpp index 6b361c4..228b803 100644 --- a/core/src/Portal.cpp +++ b/core/src/Portal.cpp @@ -315,3 +315,149 @@ int PortalNetwork::buildPortalNear(DimensionId dimension, const Vec3i& target) { bool natural = transit.findSafeSpot(dimension, target, radius, frame.height + 1, spot); if (!natural) spot = {target.x, cfg.clampY(target.y), target.z}; + if (spot.y - 1 < cfg.floorY || spot.y + frame.height >= cfg.ceilingY - 1) return -1; + + frame.origin = spot; + if (!natural || !PortalScan::siteUsable(destination.world(), frame)) { + transit.carvePlatform(dimension, spot, 3, frame.height + 2); + if (!PortalScan::siteUsable(destination.world(), frame)) return -1; + } + + PortalScan::buildFrame(destination.world(), frame); + if (PortalScan::light(destination.world(), frame) == 0) return -1; + return registerPortal(dimension, frame); +} + +PortalSearch PortalNetwork::travel(int entityId, int sourcePortalId, DimensionId to) { + PortalSearch search; + const PortalLink* source = portal(sourcePortalId); + if (!source || !source->active) return search; + if (!readyToTravel(entityId)) { + search.blocked = true; + return search; + } + + DimensionId from = source->dimension; + Vec3i position = source->frame.entry(); + + std::vector candidates; + std::vector candidateIds; + for (const PortalLink& link : portals) { + if (!link.active || link.dimension != to) continue; + candidates.push_back(link.frame.entry()); + candidateIds.push_back(link.id); + } + + search.plan = transit.plan(from, to, position, candidates); + + if (!search.plan.needsPortal) { + std::size_t index = static_cast(search.plan.nearestIndex); + search.found = true; + search.portalId = candidateIds[index]; + search.position = candidates[index]; + search.distanceSquared = search.plan.nearestDistanceSquared; + } else { + int raised = buildPortalNear(to, search.plan.target); + if (raised < 0) return search; + search.found = true; + search.built = true; + search.portalId = raised; + search.position = portals[static_cast(raised)].frame.entry(); + } + + linkPortals(sourcePortalId, search.portalId); + startCooldown(entityId); + return search; +} + +void PortalNetwork::tickEntity(int entityId, bool insidePortal) { + int& remaining = cooldown[entityId]; + int& charged = standing[entityId]; + + if (remaining > 0) { + --remaining; + charged = 0; + return; + } + if (insidePortal) ++charged; + else charged = 0; +} + +int PortalNetwork::standingTicks(int entityId) const { + auto it = standing.find(entityId); + return it == standing.end() ? 0 : it->second; +} + +int PortalNetwork::cooldownTicks(int entityId) const { + auto it = cooldown.find(entityId); + return it == cooldown.end() ? 0 : it->second; +} + +bool PortalNetwork::readyToTravel(int entityId) const { + return cooldownTicks(entityId) == 0 && standingTicks(entityId) >= standingThreshold; +} + +void PortalNetwork::startCooldown(int entityId) { + cooldown[entityId] = cooldownAfterTravel; + standing[entityId] = 0; +} + +int PortalNetwork::buildEndFrame(DimensionId dimension, const Vec3i& center) { + World& world = dimensions().dimension(dimension).world(); + int placed = 0; + + for (const Vec3i& offset : endRingOffsets) { + Vec3i slot = center + offset; + world.setBlock(slot.x, slot.y, slot.z, Block::EndPortalFrame); + ++placed; + } + for (int dx = -1; dx <= 1; ++dx) + for (int dz = -1; dz <= 1; ++dz) + world.setBlock(center.x + dx, center.y, center.z + dz, Block::Air); + return placed; +} + +bool PortalNetwork::hasEndEye(DimensionId dimension, const Vec3i& framePos) const { + const auto& sockets = eyes[dimensionIndex(dimension)]; + return sockets.find(framePos) != sockets.end(); +} + +bool PortalNetwork::insertEndEye(DimensionId dimension, const Vec3i& framePos) { + World& world = dimensions().dimension(dimension).world(); + if (world.blockAt(framePos.x, framePos.y, framePos.z) != Block::EndPortalFrame) return false; + if (!eyes[dimensionIndex(dimension)].insert(framePos).second) return false; + + for (const Vec3i& offset : endRingOffsets) { + Vec3i center{framePos.x - offset.x, framePos.y - offset.y, framePos.z - offset.z}; + activateEndPortal(dimension, center); + } + return true; +} + +int PortalNetwork::endEyeCount(DimensionId dimension, const Vec3i& center) const { + const World& world = dimensions().dimension(dimension).world(); + int filled = 0; + + for (const Vec3i& offset : endRingOffsets) { + Vec3i slot = center + offset; + if (world.blockAt(slot.x, slot.y, slot.z) != Block::EndPortalFrame) continue; + if (hasEndEye(dimension, slot)) ++filled; + } + return filled; +} + +bool PortalNetwork::endFrameComplete(DimensionId dimension, const Vec3i& center) const { + return endEyeCount(dimension, center) == endFrameSlots; +} + +bool PortalNetwork::activateEndPortal(DimensionId dimension, const Vec3i& center) { + if (!endFrameComplete(dimension, center)) return false; + + World& world = dimensions().dimension(dimension).world(); + for (int dx = -1; dx <= 1; ++dx) + for (int dz = -1; dz <= 1; ++dz) + world.setBlock(center.x + dx, center.y, center.z + dz, Block::EndPortal); + return true; +} + +} // namespace vf