From bf3953325e904c99d85b8262b325f65cc3b45f63 Mon Sep 17 00:00:00 2001 From: dam <27978131-real_damighty@users.noreply.gitlab.com> Date: Mon, 20 Jul 2026 12:34:35 +0300 Subject: [PATCH] Small teardown tweaks for familiar POH entry --- .../skill/construction/HouseManager.java | 19 ++++++- .../global/skill/construction/HouseZone.java | 19 +++---- .../main/core/game/world/map/RegionManager.kt | 17 ++++++ .../construction/HouseRegionLifecycleTests.kt | 57 +++++++++++++++++++ 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 Server/src/test/kotlin/content/global/skill/construction/HouseRegionLifecycleTests.kt diff --git a/Server/src/main/content/global/skill/construction/HouseManager.java b/Server/src/main/content/global/skill/construction/HouseManager.java index 4db8e8191..eebcdd604 100644 --- a/Server/src/main/content/global/skill/construction/HouseManager.java +++ b/Server/src/main/content/global/skill/construction/HouseManager.java @@ -43,6 +43,11 @@ public final class HouseManager { */ private DynamicRegion dungeonRegion; + /** + * If the current regions have been handed off to delayed teardown. + */ + private boolean teardownPending; + /** * The house location. */ @@ -368,9 +373,21 @@ public final class HouseManager { } ZoneBuilder.configure(zone); + teardownPending = false; return houseRegion; } + /** + * Marks the current regions as unavailable for new entrants and returns the + * exact instances that the delayed teardown must remove. + * + * @return The current house region followed by its optional dungeon region. + */ + Region[] beginTeardown() { + teardownPending = true; + return new Region[] { houseRegion, dungeonRegion }; + } + private DynamicRegion getPreparedRegion() { ZoneBorders borders = DynamicRegion.reserveArea(8,8); DynamicRegion region = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6); @@ -708,7 +725,7 @@ public final class HouseManager { // return (houseRegion != null) || (dungeonRegion != null); //} public boolean isLoaded() { - return (houseRegion != null && houseRegion.isActive()) || (dungeonRegion != null && dungeonRegion.isActive()); + return !teardownPending && ((houseRegion != null && houseRegion.isActive()) || (dungeonRegion != null && dungeonRegion.isActive())); } /** diff --git a/Server/src/main/content/global/skill/construction/HouseZone.java b/Server/src/main/content/global/skill/construction/HouseZone.java index 992640811..1c41001a6 100644 --- a/Server/src/main/content/global/skill/construction/HouseZone.java +++ b/Server/src/main/content/global/skill/construction/HouseZone.java @@ -128,19 +128,16 @@ public final class HouseZone extends MapZone { // The below tears down the house if the owner was the one who left if (house == p.getHouseManager()) { house.expelGuests(p); - int toRemove = previousRegion; - int dungRemove = previousDungeon; + Region[] regions = house.beginTeardown(); + Region houseToRemove = regions[0]; + Region dungeonToRemove = regions[1]; submitWorldPulse(new Pulse(1) { public boolean pulse() { - Region r = RegionManager.forId(toRemove); - Region dr = dungRemove != -1 ? RegionManager.forId(dungRemove) : null; - RegionManager.removeRegion(toRemove); - unregisterRegion(toRemove); - r.flagInactive(); - if (dungRemove != -1) { - RegionManager.removeRegion(dungRemove); - unregisterRegion(dungRemove); - dr.flagInactive(); + RegionManager.removeRegion(houseToRemove); + unregisterRegion(houseToRemove.getId()); + if (dungeonToRemove != null) { + RegionManager.removeRegion(dungeonToRemove); + unregisterRegion(dungeonToRemove.getId()); } return true; } diff --git a/Server/src/main/core/game/world/map/RegionManager.kt b/Server/src/main/core/game/world/map/RegionManager.kt index 309c582a9..ba5e31ccb 100644 --- a/Server/src/main/core/game/world/map/RegionManager.kt +++ b/Server/src/main/core/game/world/map/RegionManager.kt @@ -907,6 +907,23 @@ object RegionManager { } } + /** + * Removes a region only if it is still the cached instance for its id. + * + * Delayed cleanup tasks must use this overload so they cannot remove a newer + * dynamic region that has since been assigned the same coordinates. + */ + @JvmStatic + fun removeRegion(region: Region) { + if (lock.tryLock() || LOCK.tryLock(10000, TimeUnit.MILLISECONDS)) { + if (REGION_CACHE[region.id] === region) { + REGION_CACHE.remove(region.id) + } + region.flagInactive(true) + LOCK.unlock() + } + } + /** * Gets the regionCache. * @return The regionCache. diff --git a/Server/src/test/kotlin/content/global/skill/construction/HouseRegionLifecycleTests.kt b/Server/src/test/kotlin/content/global/skill/construction/HouseRegionLifecycleTests.kt new file mode 100644 index 000000000..f61321265 --- /dev/null +++ b/Server/src/test/kotlin/content/global/skill/construction/HouseRegionLifecycleTests.kt @@ -0,0 +1,57 @@ +package content.global.skill.construction + +import TestUtils +import core.game.world.map.Region +import core.game.world.map.RegionManager +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotSame +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class HouseRegionLifecycleTests { + companion object { + init { + TestUtils.preTestSetup() + } + } + + @Test + fun regionPendingTeardownIsNotReusedByImmediateReentry() { + TestUtils.getMockPlayer("house-region-reentry").use { player -> + val house = player.houseManager + house.createNewHouseAt(HouseLocation.RIMMINGTON) + val oldRegion = house.construct() + + val regionsToRemove = house.beginTeardown() + + assertSame(oldRegion, regionsToRemove[0]) + assertFalse(house.isLoaded) + + house.preEnter(player, false) + val replacement = house.houseRegion + + assertNotSame(oldRegion, replacement) + assertTrue(house.isLoaded) + RegionManager.removeRegion(oldRegion) + assertSame(replacement, house.houseRegion) + assertTrue(replacement.isActive) + + RegionManager.removeRegion(replacement) + } + } + + @Test + fun delayedRemovalDoesNotEvictAReplacementAtTheSameRegionId() { + val house = HouseManager() + house.createNewHouseAt(HouseLocation.RIMMINGTON) + val oldRegion = house.construct() + val replacement = Region(oldRegion.x, oldRegion.y) + RegionManager.addRegion(replacement.id, replacement) + + RegionManager.removeRegion(oldRegion) + + assertSame(replacement, RegionManager.regionCache[replacement.id]) + RegionManager.removeRegion(replacement.id) + } +}