diff --git a/tests/testDimension.cpp b/tests/testDimension.cpp index a701461..b0612b8 100644 --- a/tests/testDimension.cpp +++ b/tests/testDimension.cpp @@ -154,3 +154,162 @@ VF_TEST(standingSpotNeedsSolidGroundAndHeadroom) { DimensionSet dimensions(3u); World& world = dimensions.overworld().world(); + for (int gy = 0; gy <= 40; ++gy) world.setBlock(10, gy, 10, Block::Stone); + int feetY = 0; + CHECK(dimensions.overworld().standingSpot(10, 10, 0, 3, feetY)); + CHECK_EQ(feetY, 41); + + // A ceiling one block above the ground leaves no room to stand. + world.setBlock(10, 42, 10, Block::Stone); + CHECK(dimensions.overworld().standingSpot(10, 10, 0, 3, feetY)); + CHECK_EQ(feetY, 43); + + CHECK(!dimensions.overworld().standingSpot(11, 11, 0, 3, feetY)); + CHECK_EQ(dimensions.overworld().highestSolidY(10, 10), 42); + CHECK_EQ(dimensions.overworld().highestSolidY(11, 11), + dimensions.overworld().config().floorY - 1); +} + +VF_TEST(netherTravelDividesHorizontalCoordinatesByEight) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + + TravelResult result = travel.plan(DimensionId::Overworld, DimensionId::Nether, + {800, 60, -400}); + CHECK_EQ(result.target.x, 100); + CHECK_EQ(result.target.z, -50); + CHECK_EQ(result.target.y, 60); + CHECK(!result.clampedToFloor); + CHECK(!result.clampedToCeiling); + CHECK_EQ(result.searchRadius, 128); + CHECK(result.needsPortal); + + // Rounding is a floor, so a coordinate that is not a multiple of eight + // lands on the block below rather than being pulled towards zero. + TravelResult negative = travel.plan(DimensionId::Overworld, DimensionId::Nether, + {-9, 60, -1}); + CHECK_EQ(negative.target.x, -2); + CHECK_EQ(negative.target.z, -1); +} + +VF_TEST(netherTravelMultipliesBackByEightOnTheWayHome) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + + TravelResult home = travel.plan(DimensionId::Nether, DimensionId::Overworld, + {100, 60, -50}); + CHECK_EQ(home.target.x, 800); + CHECK_EQ(home.target.z, -400); + CHECK_EQ(home.searchRadius, 16); + + Vec3i start{1600, 70, 2400}; + TravelResult down = travel.plan(DimensionId::Overworld, DimensionId::Nether, start); + TravelResult up = travel.plan(DimensionId::Nether, DimensionId::Overworld, down.target); + CHECK_EQ(up.target.x, start.x); + CHECK_EQ(up.target.z, start.z); +} + +VF_TEST(travelBetweenEqualScalesLeavesCoordinatesAlone) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + + TravelResult result = travel.plan(DimensionId::Overworld, DimensionId::End, {123, 64, -77}); + CHECK_EQ(result.target.x, 123); + CHECK_EQ(result.target.z, -77); + CHECK_EQ(result.target.y, 64); + CHECK_EQ(result.searchRadius, 16); +} + +VF_TEST(travelClampsArrivalIntoTheDestinationBounds) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + DimensionConfig nether = DimensionConfig::forDimension(DimensionId::Nether); + + TravelResult high = travel.plan(DimensionId::Overworld, DimensionId::Nether, {0, 300, 0}); + CHECK(high.clampedToCeiling); + CHECK(!high.clampedToFloor); + CHECK_EQ(high.target.y, nether.highestPlaceableY()); + CHECK_EQ(high.scaledTarget.y, 300); + + TravelResult low = travel.plan(DimensionId::Overworld, DimensionId::Nether, {0, -12, 0}); + CHECK(low.clampedToFloor); + CHECK_EQ(low.target.y, nether.lowestPlaceableY()); +} + +VF_TEST(travelReusesAKnownPortalOnlyInsideTheSearchRadius) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + + std::vector nearby{{500, 60, 500}, {104, 62, -48}}; + TravelResult reuse = travel.plan(DimensionId::Overworld, DimensionId::Nether, + {800, 60, -400}, nearby); + CHECK(!reuse.needsPortal); + CHECK_EQ(reuse.nearestIndex, 1); + CHECK_EQ(reuse.nearestDistanceSquared, 4 * 4 + 2 * 2 + 2 * 2); + + std::vector faraway{{500, 60, 500}}; + TravelResult build = travel.plan(DimensionId::Overworld, DimensionId::Nether, + {800, 60, -400}, faraway); + CHECK(build.needsPortal); + CHECK_EQ(build.nearestIndex, -1); + + // The overworld radius is far tighter, so the same offset no longer counts. + std::vector homeSide{{820, 60, -400}}; + TravelResult tooFar = travel.plan(DimensionId::Nether, DimensionId::Overworld, + {100, 60, -50}, homeSide); + CHECK(tooFar.needsPortal); +} + +VF_TEST(travelPicksTheClosestOfSeveralCandidates) { + DimensionSet dimensions(1u); + DimensionTravel travel(dimensions); + + std::vector candidates{{110, 60, -50}, {102, 60, -50}, {100, 60, -56}}; + TravelResult result = travel.plan(DimensionId::Overworld, DimensionId::Nether, + {800, 60, -400}, candidates); + CHECK(!result.needsPortal); + CHECK_EQ(result.nearestIndex, 1); + CHECK_EQ(result.nearestDistanceSquared, 4); +} + +VF_TEST(findSafeSpotPrefersGroundNearTheArrivalHeight) { + DimensionSet dimensions(17u); + World& world = dimensions.overworld().world(); + + for (int dx = -1; dx <= 1; ++dx) { + for (int dz = -1; dz <= 1; ++dz) { + world.setBlock(50 + dx, 5, 50 + dz, Block::Stone); + world.setBlock(50 + dx, 60, 50 + dz, Block::Stone); + } + } + + DimensionTravel travel(dimensions); + Vec3i spot; + CHECK(travel.findSafeSpot(DimensionId::Overworld, {50, 58, 50}, 4, 4, spot)); + CHECK_EQ(spot.y, 61); + CHECK_EQ(spot.x, 50); + CHECK_EQ(spot.z, 50); + + // Nothing at all within reach means the caller has to build its own floor. + CHECK(!travel.findSafeSpot(DimensionId::Overworld, {900, 60, 900}, 3, 4, spot)); +} + +VF_TEST(carvePlatformLaysAFloorAndClearsHeadroom) { + DimensionSet dimensions(19u); + DimensionTravel travel(dimensions); + + for (int gy = 40; gy < 50; ++gy) + for (int dx = -4; dx <= 4; ++dx) + for (int dz = -4; dz <= 4; ++dz) + dimensions.end().world().setBlock(dx, gy, dz, Block::EndStone); + + travel.carvePlatform(DimensionId::End, {0, 45, 0}, 2, 4); + const World& world = dimensions.end().world(); + + CHECK_EQ(world.blockAt(0, 44, 0), Block::Obsidian); + CHECK_EQ(world.blockAt(2, 44, -2), Block::Obsidian); + CHECK_EQ(world.blockAt(0, 45, 0), Block::Air); + CHECK_EQ(world.blockAt(0, 48, 0), Block::Air); + CHECK_EQ(world.blockAt(0, 49, 0), Block::EndStone); + CHECK_EQ(world.blockAt(3, 44, 0), Block::EndStone); +}