diff --git a/src/main/java/net/tfminecraft/vfbuilders/managers/StationManager.java b/src/main/java/net/tfminecraft/vfbuilders/managers/StationManager.java index 775ca21..840ad63 100644 --- a/src/main/java/net/tfminecraft/vfbuilders/managers/StationManager.java +++ b/src/main/java/net/tfminecraft/vfbuilders/managers/StationManager.java @@ -240,6 +240,10 @@ public void inventoryClick(InventoryClickEvent e) { return; } p.closeInventory(); + if (!isRegistered(h.getStation())) { + p.sendMessage("§cThe station was removed, so nothing can be built there."); + return; + } ActivePlacement placement = new ActivePlacement(p, h.getStation(), b); activePlacements.put(p.getUniqueId(), placement); @@ -263,6 +267,12 @@ public void onPlayerLeftClick(PlayerInteractEvent e) { Location stationLoc = placement.getStation().getLocation(); Location clickLoc = p.getLocation(); + if (!isRegistered(placement.getStation())) { + activePlacements.remove(uuid); + p.sendMessage("§cBuilding site choice cancelled: the station was removed."); + return; + } + if (!java.util.Objects.equals(clickLoc.getWorld(), stationLoc.getWorld()) || clickLoc.distanceSquared(stationLoc) > Math.pow(Cache.constructionDistance, 2)) { p.sendMessage("§cToo far from the station! (max "+Cache.constructionDistance+" blocks)"); @@ -430,7 +440,17 @@ private boolean remove(Location loc) { stations.put(loc, station); return false; } + // A site choice for the removed station must not start a build on it later. + activePlacements.values().removeIf(placement -> { + if (placement.getStation() != station) return false; + placement.getPlayer().sendMessage("§cBuilding site choice cancelled: the station was removed."); + return true; + }); return true; } + private boolean isRegistered(ActiveStation station) { + return stations.get(station.getLocation()) == station; + } + } diff --git a/src/test/java/net/tfminecraft/vfbuilders/ManagerTest.java b/src/test/java/net/tfminecraft/vfbuilders/ManagerTest.java index 1f40bf0..d183961 100644 --- a/src/test/java/net/tfminecraft/vfbuilders/ManagerTest.java +++ b/src/test/java/net/tfminecraft/vfbuilders/ManagerTest.java @@ -504,4 +504,102 @@ void particleTrailStopsAfterCommitOrAnotherConstructionAndIgnoresOtherWorlds() t manager.onBlockBreak(new BlockBreakEvent(block, player)); verify(b).drop(any()); } + + String drainMessagesUntil(String fragment) { + var mock = (org.mockbukkit.mockbukkit.entity.PlayerMock) player; + for (String m = mock.nextMessage(); m != null; m = mock.nextMessage()) { + if (m.contains(fragment)) return m; + } + return null; + } + + @Test + void breakingAnIdleStationDuringSiteChoiceCannotStartABuildOnIt() throws Exception { + var s = manager.getStation(block); + var b = mock(Blueprint.class); + when(b.hasInputs(player)).thenReturn(true); + BlueprintLoader.get().put("windrider", b); + player.teleport(block.getLocation()); + manager.inventoryClick( + click(s, tagged("vfb_blueprint_id", "windrider"), net.tfminecraft.vfbuilders.enums.VFBGUI.BLUEPRINT)); + assertNotNull(drainMessagesUntil("and left-click")); + + manager.onBlockBreak(new BlockBreakEvent(block, player)); + + assertTrue(placements().isEmpty()); + assertNotNull(drainMessagesUntil("the station was removed")); + manager.onPlayerLeftClick(left()); + verify(b, never()).takeInputs(player); + assertFalse(s.hasBlueprint()); + assertNull(s.getSpawnLocation()); + } + + @Test + void stalePlacementOnARemovedStationIsDroppedEvenIfAStationReturnsThere() throws Exception { + var old = manager.getStation(block); + var b = mock(Blueprint.class); + when(b.hasInputs(player)).thenReturn(true); + manager.onBlockBreak(new BlockBreakEvent(block, player)); + var replacement = manager.getStation(block); + assertNotSame(old, replacement); + player.teleport(block.getLocation()); + placements().put(player.getUniqueId(), new ActivePlacement(player, old, b)); + + manager.onPlayerLeftClick(left()); + + assertTrue(placements().isEmpty()); + verify(b, never()).takeInputs(player); + assertFalse(old.hasBlueprint()); + assertFalse(replacement.hasBlueprint()); + assertNotNull(drainMessagesUntil("the station was removed")); + } + + @Test + void blueprintMenuLeftOpenOnARemovedStationStartsNoSiteChoice() throws Exception { + var s = manager.getStation(block); + var b = mock(Blueprint.class); + when(b.hasInputs(player)).thenReturn(true); + BlueprintLoader.get().put("boat", b); + manager.onBlockBreak(new BlockBreakEvent(block, player)); + assertNotSame(s, manager.getStation(block)); // a new station now stands on the same spot + + manager.inventoryClick( + click(s, tagged("vfb_blueprint_id", "boat"), net.tfminecraft.vfbuilders.enums.VFBGUI.BLUEPRINT)); + + assertTrue(placements().isEmpty()); + assertNotNull(drainMessagesUntil("nothing can be built there")); + } + + @Test + void removingAStationKeepsSiteChoicesForOtherStations() throws Exception { + var other = player.getWorld().getBlockAt(2, 64, 1); + when(api.getChecker().checkBlock(other, "v.stone")).thenReturn(true); + var s = manager.getStation(block); + var otherStation = manager.getStation(other); + var second = server.addPlayer(); + var mine = new ActivePlacement(player, s, mock(Blueprint.class)); + var theirs = new ActivePlacement(second, otherStation, mock(Blueprint.class)); + placements().put(player.getUniqueId(), mine); + placements().put(second.getUniqueId(), theirs); + + manager.onBlockBreak(new BlockBreakEvent(block, player)); + + assertNull(placements().get(player.getUniqueId())); + assertSame(theirs, placements().get(second.getUniqueId())); + } + + @Test + void failedRemovalSaveKeepsTheSiteChoiceForTheRestoredStation() throws Exception { + var s = manager.getStation(block); + var mine = new ActivePlacement(player, s, mock(Blueprint.class)); + placements().put(player.getUniqueId(), mine); + database.when(() -> Database.saveStationsNow(anyMap())).thenReturn(false); + var event = new BlockBreakEvent(block, player); + + manager.onBlockBreak(event); + + assertTrue(event.isCancelled()); + assertSame(s, manager.getStation(block)); + assertSame(mine, placements().get(player.getUniqueId())); + } }