From 81d550493290f0cbaad5726d9b21e56403c1664b Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 19:16:10 +0200 Subject: [PATCH] feat: add Portal --- core/src/Portal.cpp | 156 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 core/src/Portal.cpp diff --git a/core/src/Portal.cpp b/core/src/Portal.cpp new file mode 100644 index 0000000..3c366ca --- /dev/null +++ b/core/src/Portal.cpp @@ -0,0 +1,156 @@ +#include "vf/Portal.h" + +#include + +namespace vf { + +namespace { + +const Vec3i endRingOffsets[PortalNetwork::endFrameSlots] = { + {-2, 0, -1}, {-2, 0, 0}, {-2, 0, 1}, + {2, 0, -1}, {2, 0, 0}, {2, 0, 1}, + {-1, 0, -2}, {0, 0, -2}, {1, 0, -2}, + {-1, 0, 2}, {0, 0, 2}, {1, 0, 2}, +}; + +std::size_t dimensionIndex(DimensionId id) { + return static_cast(id); +} + +} // namespace + +Vec3i PortalFrame::interiorBlock(int across, int up) const { + if (alongX) return {origin.x + across, origin.y + up, origin.z}; + return {origin.x, origin.y + up, origin.z + across}; +} + +bool PortalFrame::containsInterior(const Vec3i& pos) const { + int up = pos.y - origin.y; + if (up < 0 || up >= height) return false; + if (alongX) { + if (pos.z != origin.z) return false; + int across = pos.x - origin.x; + return across >= 0 && across < width; + } + if (pos.x != origin.x) return false; + int across = pos.z - origin.z; + return across >= 0 && across < width; +} + +bool PortalFrame::operator==(const PortalFrame& other) const { + return origin == other.origin && width == other.width && height == other.height && + alongX == other.alongX && dimension == other.dimension; +} + +bool PortalScan::interiorCell(Block block) { + return block == Block::Air || block == Block::NetherPortal; +} + +PortalScan PortalScan::detect(const World& world, const Vec3i& probe, DimensionId dimension) { + PortalScan scan; + + for (int attempt = 0; attempt < 2; ++attempt) { + bool alongX = attempt == 0; + int plane = alongX ? probe.z : probe.x; + int seed = alongX ? probe.x : probe.z; + + auto sample = [&](int across, int y) { + return alongX ? world.blockAt(across, y, plane) : world.blockAt(plane, y, across); + }; + + if (!interiorCell(sample(seed, probe.y))) continue; + + int bottom = probe.y; + int steps = 0; + while (steps < PortalFrame::maxHeight && interiorCell(sample(seed, bottom - 1))) { + --bottom; + ++steps; + } + if (sample(seed, bottom - 1) != Block::Obsidian) continue; + + int left = seed; + steps = 0; + while (steps < PortalFrame::maxWidth && interiorCell(sample(left - 1, bottom))) { + --left; + ++steps; + } + if (sample(left - 1, bottom) != Block::Obsidian) continue; + + int right = seed; + steps = 0; + while (steps < PortalFrame::maxWidth && interiorCell(sample(right + 1, bottom))) { + ++right; + ++steps; + } + if (sample(right + 1, bottom) != Block::Obsidian) continue; + + int width = right - left + 1; + if (width < PortalFrame::minWidth || width > PortalFrame::maxWidth) continue; + + int top = bottom; + steps = 0; + while (steps < PortalFrame::maxHeight && interiorCell(sample(left, top + 1))) { + ++top; + ++steps; + } + int height = top - bottom + 1; + if (height < PortalFrame::minHeight || height > PortalFrame::maxHeight) continue; + + PortalFrame frame; + frame.alongX = alongX; + frame.dimension = dimension; + frame.width = width; + frame.height = height; + frame.origin = alongX ? Vec3i{left, bottom, plane} : Vec3i{plane, bottom, left}; + + if (!intact(world, frame) || !interiorClear(world, frame)) continue; + + scan.found = true; + scan.frame = frame; + scan.interiorArea = frame.interiorArea(); + return scan; + } + return scan; +} + +bool PortalScan::intact(const World& world, const PortalFrame& frame) { + for (int across = 0; across < frame.width; ++across) { + Vec3i low = frame.bottomRow(across); + Vec3i high = frame.topRow(across); + if (world.blockAt(low.x, low.y, low.z) != Block::Obsidian) return false; + if (world.blockAt(high.x, high.y, high.z) != Block::Obsidian) return false; + } + for (int up = 0; up < frame.height; ++up) { + Vec3i left = frame.leftColumn(up); + Vec3i right = frame.rightColumn(up); + if (world.blockAt(left.x, left.y, left.z) != Block::Obsidian) return false; + if (world.blockAt(right.x, right.y, right.z) != Block::Obsidian) return false; + } + return true; +} + +bool PortalScan::interiorClear(const World& world, const PortalFrame& frame) { + for (int across = 0; across < frame.width; ++across) { + for (int up = 0; up < frame.height; ++up) { + Vec3i cell = frame.interiorBlock(across, up); + if (!interiorCell(world.blockAt(cell.x, cell.y, cell.z))) return false; + } + } + return true; +} + +int PortalScan::light(World& world, const PortalFrame& frame) { + if (!intact(world, frame) || !interiorClear(world, frame)) return 0; + + int lit = 0; + for (int across = 0; across < frame.width; ++across) { + for (int up = 0; up < frame.height; ++up) { + Vec3i cell = frame.interiorBlock(across, up); + if (world.blockAt(cell.x, cell.y, cell.z) == Block::NetherPortal) continue; + world.setBlock(cell.x, cell.y, cell.z, Block::NetherPortal); + ++lit; + } + } + return lit; +} +