diff --git a/src/game/WorldHandlers/GridMap.cpp b/src/game/WorldHandlers/GridMap.cpp index 73a1d8337..b335527ac 100644 --- a/src/game/WorldHandlers/GridMap.cpp +++ b/src/game/WorldHandlers/GridMap.cpp @@ -352,7 +352,7 @@ GridMapLiquidStatus TerrainInfo::getLiquidStatus(float x, float y, float z, const world::terrain::Column column = ColumnAt(x, y, z + FLOOR_BURIED_LIFT, z - FLOOR_SEARCH_DOWN); - const auto liquid = column.HighestLiquid(); + const auto liquid = column.LiquidFor(z); if (!liquid || !liquid->liquidEntry) { return LIQUID_MAP_NO_WATER; diff --git a/src/shared/terrain/Column.hpp b/src/shared/terrain/Column.hpp index 3f9abd330..fea6deb84 100644 --- a/src/shared/terrain/Column.hpp +++ b/src/shared/terrain/Column.hpp @@ -29,6 +29,7 @@ namespace world::terrain LiquidKind liquid = LiquidKind::None; uint16_t liquidEntry = 0; bool deep = false; + std::optional terrainZ; ///< see LiquidInfo::terrainZ bool Solid() const { return kind != SurfaceKind::Liquid; } @@ -39,10 +40,15 @@ namespace world::terrain info.kind = liquid; info.entry = liquidEntry; info.deep = deep; + info.terrainZ = terrainZ; return info; } }; + /// How far under its ADT terrain a point may be and still be in that tile's + /// liquid. + constexpr float ADT_LIQUID_BURIED_LIMIT = 2.0f; + class Column { public: @@ -62,6 +68,7 @@ namespace world::terrain s.liquid = info.kind; s.liquidEntry = info.entry; s.deep = info.deep; + s.terrainZ = info.terrainZ; m_surfaces.push_back(s); } @@ -131,6 +138,33 @@ namespace world::terrain return best; } + /// The liquid a point at z is in, under or over: the highest one + /// that reaches it. An ADT liquid does not reach a point when it + /// lies under its own terrain, or the point is more than + /// ADT_LIQUID_BURIED_LIMIT under that terrain -- a cave, or a city + /// built below the surface (the Undercity under Tirisfal) -- as the + /// reference GridMap::getLiquidStatus had it. + std::optional LiquidFor(float z) const + { + std::optional best; + for (const Surface& s : m_surfaces) + { + if (s.kind != SurfaceKind::Liquid || + (best && s.z <= best->z)) + { + continue; + } + if (s.terrainZ && + (s.z < *s.terrainZ || + z < *s.terrainZ - ADT_LIQUID_BURIED_LIMIT)) + { + continue; + } + best = s; + } + return best; + } + private: std::vector m_surfaces; }; diff --git a/src/shared/terrain/FusedTerrain.cpp b/src/shared/terrain/FusedTerrain.cpp index 437334d75..7db4fe313 100644 --- a/src/shared/terrain/FusedTerrain.cpp +++ b/src/shared/terrain/FusedTerrain.cpp @@ -345,6 +345,9 @@ namespace world::terrain { if (auto adt = tile->LiquidAt(x, y)) { + // The terrain it lies on: a point buried under that terrain is + // not in it (Column::LiquidFor), however far below the floor. + adt->terrainZ = tile->TerrainHeight(x, y); column.AddLiquid(*adt); } } diff --git a/src/shared/terrain/Terrain.hpp b/src/shared/terrain/Terrain.hpp index 7ebd27964..92a3874c4 100644 --- a/src/shared/terrain/Terrain.hpp +++ b/src/shared/terrain/Terrain.hpp @@ -39,6 +39,9 @@ namespace world::terrain LiquidKind kind = LiquidKind::None; uint16_t entry = 0; ///< LiquidType.dbc row id (0 = unknown) bool deep = false; ///< drives swim fatigue ("dark water") + /// The ADT terrain under an ADT liquid; none for a model's liquid or + /// over a hole. + std::optional terrainZ; }; struct StaticInstance diff --git a/src/tests/TerrainModelTest.cpp b/src/tests/TerrainModelTest.cpp index bc82432fd..4a43738ae 100644 --- a/src/tests/TerrainModelTest.cpp +++ b/src/tests/TerrainModelTest.cpp @@ -26,6 +26,7 @@ #include "TestHarness.h" #include "terrain/CollisionModel.hpp" +#include "terrain/Column.hpp" #include "terrain/Terrain.hpp" #include "terrain/WmoModel.hpp" @@ -336,3 +337,77 @@ TEST(WmoAreaInfoNamesTheGroupTheRayHit) REQUIRE(below.has_value()); CHECK_EQ(below->groupId, uint32_t(111)); } + +namespace +{ + LiquidInfo AdtWater(float level, std::optional terrainZ) + { + LiquidInfo info; + info.level = level; + info.kind = LiquidKind::Water; + info.entry = 1; + info.terrainZ = terrainZ; + return info; + } +} + +TEST(AdtLiquidDoesNotReachAPointFarUnderItsTerrain) +{ + // The Undercity, Miles Dexter: Tirisfal's water at 32.93, under its own + // terrain at 34.14, the city's floor 95 yards down. The reference GridMap + // left it dry. + Column c; + c.AddSolid(-62.28f, SurfaceKind::Static); + c.AddLiquid(AdtWater(32.93f, 34.14f)); + CHECK(!c.LiquidFor(-62.2f).has_value()); + // still there for a caller that asks for any liquid + CHECK(c.HighestLiquid().has_value()); +} + +TEST(AdtLiquidReachesAPointAboveOrJustUnderItsTerrain) +{ + Column c; + c.AddSolid(10.f, SurfaceKind::Terrain); + c.AddLiquid(AdtWater(15.f, 10.f)); + const auto swimming = c.LiquidFor(12.f); + REQUIRE(swimming.has_value()); + CHECK_EQ(swimming->z, 15.f); + CHECK(c.LiquidFor(10.f - ADT_LIQUID_BURIED_LIMIT + 0.5f).has_value()); + CHECK(!c.LiquidFor(10.f - ADT_LIQUID_BURIED_LIMIT - 0.5f).has_value()); +} + +TEST(AdtLiquidUnderItsOwnTerrainIsNoLiquid) +{ + Column c; + c.AddLiquid(AdtWater(32.93f, 34.14f)); + CHECK(!c.LiquidFor(40.f).has_value()); +} + +TEST(AdtLiquidOverAHoleAndModelLiquidAreNotFiltered) +{ + // No terrain under it (a hole cut for a WMO entrance) or a WMO's own + // liquid: the floor checks of the caller decide, as before. + Column c; + c.AddLiquid(AdtWater(32.93f, std::nullopt)); + LiquidInfo wmo = AdtWater(-60.f, std::nullopt); + wmo.kind = LiquidKind::Slime; + c.AddLiquid(wmo); + const auto hole = c.LiquidFor(-62.2f); + REQUIRE(hole.has_value()); + CHECK_EQ(hole->z, 32.93f); +} + +TEST(BuriedAdtLiquidLeavesAModelLiquidBelowIt) +{ + // The canal of a city under a lake: the lake is out, the canal is the + // answer. + Column c; + c.AddLiquid(AdtWater(32.93f, 30.f)); + LiquidInfo canal = AdtWater(-63.f, std::nullopt); + canal.kind = LiquidKind::Slime; + c.AddLiquid(canal); + const auto liquid = c.LiquidFor(-64.f); + REQUIRE(liquid.has_value()); + CHECK_EQ(liquid->z, -63.f); + CHECK(liquid->liquid == LiquidKind::Slime); +} diff --git a/src/tools/height-check/HeightCheck.cpp b/src/tools/height-check/HeightCheck.cpp index 08973c505..eb5fb6b76 100644 --- a/src/tools/height-check/HeightCheck.cpp +++ b/src/tools/height-check/HeightCheck.cpp @@ -224,7 +224,7 @@ int main(int argc, char** argv) if (verbose) { - const auto liquid = column.HighestLiquid(); + const auto liquid = column.LiquidFor(p.z); char liq[80] = "none"; if (liquid) {