From 63cee5ca00c7dbe18ff6318690262d51c99eb796 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:49:16 +0800 Subject: [PATCH 01/10] Refactor listeners for code readability and fix access modifiers --- .../portals/listeners/MVPBlockListener.java | 34 ++- .../portals/listeners/MVPCoreListener.java | 44 +-- .../portals/listeners/MVPPlayerListener.java | 282 ++++++------------ .../listeners/MVPPlayerMoveListener.java | 102 ++----- .../portals/listeners/MVPPortalListener.java | 144 +++++++++ .../portals/listeners/MVPVehicleListener.java | 74 ++--- .../listeners/PlayerListenerHelper.java | 62 +++- 7 files changed, 387 insertions(+), 355 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPBlockListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPBlockListener.java index 17734b30..c6288c5e 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPBlockListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPBlockListener.java @@ -9,31 +9,51 @@ import org.bukkit.Material; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockPhysicsEvent; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.portals.config.PortalsConfig; import org.mvplugins.multiverse.portals.utils.PortalManager; @Service -public class MVPBlockListener implements PortalsListener { +final class MVPBlockListener implements PortalsListener { private final PortalManager portalManager; + private final PortalsConfig portalsConfig; @Inject - MVPBlockListener(@NotNull PortalManager portalManager) { + MVPBlockListener(@NotNull PortalManager portalManager, @NotNull PortalsConfig portalsConfig) { this.portalManager = portalManager; + this.portalsConfig = portalsConfig; } - @EventHandler - public void blockPhysics(BlockPhysicsEvent event) { - if (event.isCancelled()) { - return; - } + @EventHandler(ignoreCancelled = true) + void blockPhysics(BlockPhysicsEvent event) { if (event.getChangedType() == Material.NETHER_PORTAL || event.getBlock().getType() == Material.NETHER_PORTAL) { if (portalManager.isPortal(event.getBlock().getLocation())) { event.setCancelled(true); } } } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + void blockFromTo(BlockFromToEvent event) { + // The to block should never be null, but apparently it is sometimes... + if (event.getBlock() == null || event.getToBlock() == null) { + return; + } + + // If lava/something else is trying to flow in... + if (portalManager.isPortal(event.getToBlock().getLocation())) { + event.setCancelled(true); + return; + } + // If something is trying to flow out, stop that too, unless bucketFilling has been disabled + if (portalManager.isPortal(event.getBlock().getLocation()) && portalsConfig.getBucketFilling()) { + event.setCancelled(true); + } + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java index 9f54a7c4..ade92c39 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java @@ -22,17 +22,20 @@ import org.bukkit.event.EventHandler; import org.mvplugins.multiverse.portals.MultiversePortals; +import org.mvplugins.multiverse.portals.config.PortalsConfig; import org.mvplugins.multiverse.portals.utils.PortalManager; @Service -public class MVPCoreListener implements PortalsListener { +final class MVPCoreListener implements PortalsListener { private final MultiversePortals plugin; + private final PortalsConfig config; private final PortalManager portalManager; @Inject - MVPCoreListener(@NotNull MultiversePortals plugin, @NotNull PortalManager portalManager) { + MVPCoreListener(@NotNull MultiversePortals plugin, @NotNull PortalManager portalManager, @NotNull PortalsConfig config) { this.plugin = plugin; this.portalManager = portalManager; + this.config = config; } /** @@ -73,26 +76,27 @@ public void debugModeChange(MVDebugModeEvent event) { public void portalTouchEvent(MVPlayerTouchedPortalEvent event) { Logging.finer("Found The TouchedPortal event."); Location l = event.getBlockTouched(); - if (event.canUseThisPortal() && (this.portalManager.isPortal(l))) { - if (this.plugin.getPortalSession(event.getPlayer()).isDebugModeOn()) { - event.setCancelled(true); + if (!event.canUseThisPortal() || (!this.portalManager.isPortal(l))) { + return; + } + if (this.plugin.getPortalSession(event.getPlayer()).isDebugModeOn()) { + event.setCancelled(true); + return; + } + // This is a valid portal, and they can use it so far... + MVPortal p = this.portalManager.getPortal(event.getPlayer(), l); + if (p == null) { + // The player can't see this portal, and can't use it. + Logging.finer(String.format("'%s' was DENIED access to this portal event.", event.getPlayer().getName())); + event.setCanUseThisPortal(false); + } else if (p.getDestination() == null) { + if (config.getPortalsDefaultToNether()) { + Logging.finer("Allowing MVPortal to act as nether portal."); return; } - // This is a valid portal, and they can use it so far... - MVPortal p = this.portalManager.getPortal(event.getPlayer(), l); - if (p == null) { - // The player can't see this portal, and can't use it. - Logging.finer(String.format("'%s' was DENIED access to this portal event.", event.getPlayer().getName())); - event.setCanUseThisPortal(false); - } else if (p.getDestination() == null) { - if (this.plugin.getMainConfig().getBoolean("portalsdefaulttonether", false)) { - Logging.finer("Allowing MVPortal to act as nether portal."); - return; - } - // They can see it, is it val - event.getPlayer().sendMessage("This Multiverse Portal does not have a valid destination!"); - event.setCanUseThisPortal(false); - } + // They can see it, is it val + event.getPlayer().sendMessage("This Multiverse Portal does not have a valid destination!"); + event.setCanUseThisPortal(false); } } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerListener.java index a956e4ce..8712e8f8 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerListener.java @@ -43,47 +43,38 @@ import org.bukkit.inventory.EquipmentSlot; @Service -public class MVPPlayerListener implements PortalsListener { +final class MVPPlayerListener implements PortalsListener { private final MultiversePortals plugin; private final PortalsConfig portalsConfig; private final PortalFiller filler; private final PortalManager portalManager; - private final PlayerListenerHelper helper; private final LocationManipulation locationManipulation; private final WorldManager worldManager; - private final BlockSafety blockSafety; - private final MVEconomist economist; @Inject MVPPlayerListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, - @NotNull PlayerListenerHelper helper, @NotNull PortalManager portalManager, @NotNull PortalFiller filler, @NotNull LocationManipulation locationManipulation, - @NotNull WorldManager worldManager, - @NotNull BlockSafety blockSafety, - @NotNull MVEconomist economist) { + @NotNull WorldManager worldManager) { this.plugin = plugin; this.portalsConfig = portalsConfig; - this.helper = helper; this.portalManager = portalManager; this.filler = filler; this.locationManipulation = locationManipulation; this.worldManager = worldManager; - this.blockSafety = blockSafety; - this.economist = economist; } @EventHandler - public void playerQuit(PlayerQuitEvent event) { + void playerQuit(PlayerQuitEvent event) { this.plugin.destroyPortalSession(event.getPlayer()); } @EventHandler(priority = EventPriority.MONITOR) - public void playerTeleport(PlayerTeleportEvent event) { + void playerTeleport(PlayerTeleportEvent event) { if (event.isCancelled()) { Logging.fine("The PlayerTeleportEvent was already cancelled. Doing nothing."); return; @@ -93,7 +84,7 @@ public void playerTeleport(PlayerTeleportEvent event) { } @EventHandler(priority = EventPriority.LOW) - public void playerBucketFill(PlayerBucketFillEvent event) { + void playerBucketFill(PlayerBucketFillEvent event) { if (event.isCancelled()) { Logging.fine("The PlayerBucketFillEvent was already cancelled. Doing nothing."); return; @@ -104,20 +95,25 @@ public void playerBucketFill(PlayerBucketFillEvent event) { PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); MVPortal portal = portalManager.getPortal(event.getPlayer(), event.getBlockClicked().getLocation()); - if (portal != null) { - if (ps.isDebugModeOn()) { - ps.showDebugInfo(portal); - event.setCancelled(true); - } else { - Material fillMaterial = Material.AIR; - Logging.finer("Fill Material: " + fillMaterial); - this.filler.fillRegion(portal.getPortalLocation().getRegion(), event.getBlockClicked().getLocation(), fillMaterial, event.getPlayer()); - } + if (portal == null) { + return; + } + if (ps.isDebugModeOn()) { + ps.showDebugInfo(portal); + event.setCancelled(true); + return; } + Material fillMaterial = Material.AIR; + Logging.finer("Fill Material: " + fillMaterial); + this.filler.fillRegion( + portal.getPortalLocation().getRegion(), + event.getBlockClicked().getLocation(), + fillMaterial, + event.getPlayer()); } @EventHandler(priority = EventPriority.LOW) - public void playerBucketEmpty(PlayerBucketEmptyEvent event) { + void playerBucketEmpty(PlayerBucketEmptyEvent event) { if (event.isCancelled()) { Logging.fine("The PlayerBucketEmptyEvent was already cancelled. Doing nothing."); return; @@ -135,28 +131,30 @@ public void playerBucketEmpty(PlayerBucketEmptyEvent event) { PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); MVPortal portal = portalManager.getPortal(event.getPlayer(), translatedLocation); - if (portal != null) { - if (ps.isDebugModeOn()) { - ps.showDebugInfo(portal); - event.setCancelled(true); - } else { - if (!portal.playerCanFillPortal(event.getPlayer())) { - event.setCancelled(true); - return; - } - Material fillMaterial = Material.WATER; - if (event.getBucket().equals(Material.LAVA_BUCKET)) { - fillMaterial = Material.LAVA; - } + if (portal == null) { + return; + } - Logging.finer("Fill Material: " + fillMaterial); - this.filler.fillRegion(portal.getPortalLocation().getRegion(), translatedLocation, fillMaterial, event.getPlayer()); - } + if (ps.isDebugModeOn()) { + ps.showDebugInfo(portal); + event.setCancelled(true); + return; + } + if (!portal.playerCanFillPortal(event.getPlayer())) { + event.setCancelled(true); + return; + } + Material fillMaterial = Material.WATER; + if (event.getBucket().equals(Material.LAVA_BUCKET)) { + fillMaterial = Material.LAVA; } + + Logging.finer("Fill Material: " + fillMaterial); + this.filler.fillRegion(portal.getPortalLocation().getRegion(), translatedLocation, fillMaterial, event.getPlayer()); } @EventHandler(priority = EventPriority.LOW) - public void playerInteract(PlayerInteractEvent event) { + void playerInteract(PlayerInteractEvent event) { if (event.isCancelled()) { Logging.fine("The PlayerInteractEvent was already cancelled. Doing nothing."); return; @@ -164,48 +162,7 @@ public void playerInteract(PlayerInteractEvent event) { // Portal lighting stuff if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getMaterial() == Material.FLINT_AND_STEEL) { - // They're lighting somethin' - Logging.finer("Player is lighting block: " + this.locationManipulation.strCoordsRaw(event.getClickedBlock().getLocation())); - PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); - Location translatedLocation = this.getTranslatedLocation(event.getClickedBlock(), event.getBlockFace()); - if (!portalManager.isPortal(translatedLocation)) { - return; - } - MVPortal portal = portalManager.getPortal(event.getPlayer(), translatedLocation); - if (event.getItem() == null) { - return; - } - if (!event.getPlayer().hasPermission("multiverse.portal.create")) { - return; - } - Material inHand = event.getItem().getType(); - - // Cancel the event if there was a portal. - - if (portal != null) { - - // Make sure the portal's frame around this point is made out of - // a valid material. - if (!portal.isFrameValid(translatedLocation)) { - return; - } - - Logging.finer("Right Clicked: "); - Logging.finer("Block Clicked: " + event.getClickedBlock() + ":" + event.getClickedBlock().getType()); - Logging.finer("Translated Block: " + event.getPlayer().getWorld().getBlockAt(translatedLocation) + ":" + event.getPlayer().getWorld().getBlockAt(translatedLocation).getType()); - Logging.finer("In Hand: " + inHand); - if (ps.isDebugModeOn()) { - ps.showDebugInfo(portal); - event.setCancelled(true); - } else { - Material fillMaterial = Material.NETHER_PORTAL; - if (translatedLocation.getWorld().getBlockAt(translatedLocation).getType() == Material.NETHER_PORTAL) { - fillMaterial = Material.AIR; - } - Logging.finer("Fill Material: " + fillMaterial); - event.setCancelled(this.filler.fillRegion(portal.getPortalLocation().getRegion(), translatedLocation, fillMaterial, event.getPlayer())); - } - } + lightPortalWithFlintAndSteel(event); return; } @@ -231,123 +188,60 @@ public void playerInteract(PlayerInteractEvent event) { } } - private Location getTranslatedLocation(Block clickedBlock, BlockFace face) { - Location clickedLoc = clickedBlock.getLocation(); - Location newLoc = new Location(clickedBlock.getWorld(), face.getModX() + clickedLoc.getBlockX(), face.getModY() + clickedLoc.getBlockY(), face.getModZ() + clickedLoc.getBlockZ()); - Logging.finest("Clicked Block: " + clickedBlock.getLocation()); - Logging.finest("Translated Block: " + newLoc); - return newLoc; - } - - @EventHandler - public void playerPortal(PlayerPortalEvent event) { - if (event.isCancelled()) { - Logging.fine("This Portal event was already cancelled."); + private void lightPortalWithFlintAndSteel(PlayerInteractEvent event) { + // They're lighting somethin' + Logging.finer("Player is lighting block: " + this.locationManipulation.strCoordsRaw(event.getClickedBlock().getLocation())); + PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); + Location translatedLocation = this.getTranslatedLocation(event.getClickedBlock(), event.getBlockFace()); + if (!portalManager.isPortal(translatedLocation)) { return; } - Logging.finer("onPlayerPortal called!"); - Location playerPortalLoc = event.getPlayer().getLocation(); - // Determine if we're in a portal - MVPortal portal = portalManager.getPortal(event.getPlayer(), playerPortalLoc, false); - Player p = event.getPlayer(); - // Even if the location was null, we still have to see if - // someone wasn't exactly on (because they can do this). - if (portal == null) { - // Check around the player to make sure - playerPortalLoc = this.blockSafety.findPortalBlockNextTo(event.getFrom()); - if (playerPortalLoc != null) { - Logging.finer("Player was outside of portal, The location has been successfully translated."); - portal = portalManager.getPortal(event.getPlayer(), playerPortalLoc, false); - } + MVPortal portal = portalManager.getPortal(event.getPlayer(), translatedLocation); + if (event.getItem() == null) { + return; } - if (portal != null) { - Logging.finer("There was a portal found!"); - DestinationInstance portalDest = portal.getDestination(); - if (portalDest != null) { - // this is a valid MV Portal, so we'll cancel the event - event.setCancelled(true); - - if (!portal.isFrameValid(playerPortalLoc)) { - event.getPlayer().sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now."); - return; - } - - Location destLocation = portalDest.getLocation(event.getPlayer()).getOrNull(); - if (destLocation == null) { - Logging.fine("Unable to teleport player because destination is null!"); - return; - } - - if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { - Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); - return; - } - - if (portal.getCheckDestinationSafety() && portalDest.checkTeleportSafety()) { - Location safeLocation = blockSafety.findSafeSpawnLocation(portalDest.getLocation(event.getPlayer()).getOrNull()); - if (safeLocation == null) { - event.setCancelled(true); - Logging.warning("Portal " + portal.getName() + " destination is not safe!"); - event.getPlayer().sendMessage(ChatColor.RED + "Portal " + portal.getName() + " destination is not safe!"); - return; - } - destLocation = safeLocation; - } - event.setTo(destLocation); - - PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); - - if (ps.checkAndSendCooldownMessage()) { - Logging.fine("Player denied teleportation due to cooldown."); - return; - } - // If they're using Access and they don't have permission and they're NOT exempt, return, they're not allowed to tp. - // No longer checking exemption status - if (portalsConfig.getEnforcePortalAccess() && !event.getPlayer().hasPermission(portal.getPermission())) { - this.helper.stateFailure(p.getDisplayName(), portal.getName()); - return; - } - - boolean shouldPay = false; - double price = portal.getPrice(); - Material currency = portal.getCurrency(); - - // Stop the player if the portal costs and they can't pay - if (price != 0D && !p.hasPermission(portal.getExempt())) { - shouldPay = true; - if (price > 0D && !economist.isPlayerWealthyEnough(p, price, currency)) { - p.sendMessage(economist.getNSFMessage(currency, - "You need " + economist.formatPrice(price, currency) + " to enter the " + portal.getName() + " portal.")); - return; - } - } + if (!event.getPlayer().hasPermission("multiverse.portal.create")) { + return; + } + Material inHand = event.getItem().getType(); - MVPortalEvent portalEvent = new MVPortalEvent(portalDest, event.getPlayer(), portal); - this.plugin.getServer().getPluginManager().callEvent(portalEvent); + // Cancel the event if there was a portal. + if (portal == null) { + return; + } - if (portalEvent.isCancelled()) { - Logging.fine("Someone cancelled the MVPlayerPortal Event!"); - return; - } else if (shouldPay) { - if (price < 0D) { - economist.deposit(p, -price, currency); - } else { - economist.withdraw(p, price, currency); - } - p.sendMessage(String.format("You have %s %s for using %s.", - price > 0D ? "been charged" : "earned", - economist.formatPrice(price, currency), - portal.getName())); - } + // Make sure the portal's frame around this point is made out of + // a valid material. + if (!portal.isFrameValid(translatedLocation)) { + return; + } - event.getPlayer().teleport(event.getTo()); - } else if (!portalsConfig.getPortalsDefaultToNether()) { - // If portals should not default to the nether, cancel the event - event.getPlayer().sendMessage(String.format( - "This portal %sdoesn't go anywhere. You should exit it now.", ChatColor.RED)); - Logging.fine("Event canceled because this was a MVPortal with an invalid destination. But you had 'portalsdefaulttonether' set to false!"); - event.setCancelled(true); - } + Logging.finer("Right Clicked: "); + Logging.finer("Block Clicked: " + event.getClickedBlock() + ":" + event.getClickedBlock().getType()); + Logging.finer("Translated Block: " + event.getPlayer().getWorld().getBlockAt(translatedLocation) + ":" + event.getPlayer().getWorld().getBlockAt(translatedLocation).getType()); + Logging.finer("In Hand: " + inHand); + if (ps.isDebugModeOn()) { + ps.showDebugInfo(portal); + event.setCancelled(true); + return; + } + Material fillMaterial = Material.NETHER_PORTAL; + if (translatedLocation.getWorld().getBlockAt(translatedLocation).getType() == Material.NETHER_PORTAL) { + fillMaterial = Material.AIR; } + Logging.finer("Fill Material: " + fillMaterial); + event.setCancelled(this.filler.fillRegion( + portal.getPortalLocation().getRegion(), + translatedLocation, + fillMaterial, + event.getPlayer())); + } + + private Location getTranslatedLocation(Block clickedBlock, BlockFace face) { + Location clickedLoc = clickedBlock.getLocation(); + Location newLoc = new Location(clickedBlock.getWorld(), face.getModX() + clickedLoc.getBlockX(), face.getModY() + clickedLoc.getBlockY(), face.getModZ() + clickedLoc.getBlockZ()); + Logging.finest("Clicked Block: " + clickedBlock.getLocation()); + Logging.finest("Translated Block: " + newLoc); + return newLoc; } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java index 4cc1abc1..1c623da7 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java @@ -10,7 +10,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.event.Listener; import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -23,70 +22,35 @@ import org.mvplugins.multiverse.portals.event.MVPortalEvent; import org.bukkit.ChatColor; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.player.PlayerMoveEvent; -import org.mvplugins.multiverse.portals.utils.PortalManager; @Service -public class MVPPlayerMoveListener implements Listener { +final public class MVPPlayerMoveListener implements Listener { private final MultiversePortals plugin; private final PortalsConfig portalsConfig; private final PlayerListenerHelper helper; - private final PortalManager portalManager; private final WorldManager worldManager; - private final MVEconomist economist; @Inject MVPPlayerMoveListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, @NotNull PlayerListenerHelper helper, - @NotNull PortalManager portalManager, - @NotNull WorldManager worldManager, - @NotNull MVEconomist economist) { + @NotNull WorldManager worldManager) { this.plugin = plugin; this.portalsConfig = portalsConfig; this.helper = helper; - this.portalManager = portalManager; this.worldManager = worldManager; - this.economist = economist; } - @EventHandler(priority = EventPriority.LOW) - public void blockFromTo(BlockFromToEvent event) { - // Always check if the event has been canceled by someone else. - if(event.isCancelled()) { - return; - } - - // The to block should never be null, but apparently it is sometimes... - if (event.getBlock() == null || event.getToBlock() == null) { - return; - } - - // If lava/something else is trying to flow in... - if (portalManager.isPortal(event.getToBlock().getLocation())) { - event.setCancelled(true); - return; - } - // If something is trying to flow out, stop that too, unless bucketFilling has been disabled - if (portalManager.isPortal(event.getBlock().getLocation()) && portalsConfig.getBucketFilling()) { - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOW) - public void playerMove(PlayerMoveEvent event) { - if (event.isCancelled()) { - return; - } - Player p = event.getPlayer(); // Grab Player - Location loc = p.getLocation(); // Grab Location + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + void playerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); // Grab Player + Location loc = player.getLocation(); // Grab Location // Check the Player has actually moved a block to prevent unneeded calculations... This is to prevent huge performance drops on high player count servers. PortalPlayerSession ps = this.plugin.getPortalSession(event.getPlayer()); @@ -109,9 +73,9 @@ public void playerMove(PlayerMoveEvent event) { Logging.fine("Invalid Destination!"); return; } - p.setFallDistance(0); + player.setFallDistance(0); - Location destLocation = d.getLocation(p).getOrNull(); + Location destLocation = d.getLocation(player).getOrNull(); if (destLocation == null) { Logging.fine("Unable to teleport player because destination is null!"); return; @@ -122,51 +86,35 @@ public void playerMove(PlayerMoveEvent event) { return; } if (!portal.isFrameValid(loc)) { - p.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material. You should exit it now."); + player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material. You should exit it now."); return; } if (ps.checkAndSendCooldownMessage()) { return; } + + PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + if (!portalUseResult.canUse()) { + return; + } + // If they're using Access and they don't have permission and they're NOT excempt, return, they're not allowed to tp. // No longer checking exemption status if (portalsConfig.getEnforcePortalAccess() && !event.getPlayer().hasPermission(portal.getPermission())) { - this.helper.stateFailure(p.getDisplayName(), portal.getName()); + this.helper.stateFailure(player.getDisplayName(), portal.getName()); return; } - double price = portal.getPrice(); - Material currency = portal.getCurrency(); - - if (price != 0D && !p.hasPermission(portal.getExempt())) { - if (price < 0D || economist.isPlayerWealthyEnough(p, price, currency)) { - // call event for other plugins - MVPortalEvent portalEvent = new MVPortalEvent(d, event.getPlayer(), portal); - this.plugin.getServer().getPluginManager().callEvent(portalEvent); - if (!portalEvent.isCancelled()) { - if (price < 0D) { - economist.deposit(p, -price, currency); - } else { - economist.withdraw(p, price, currency); - } - p.sendMessage(String.format("You have %s %s for using %s.", - price > 0D ? "been charged" : "earned", - economist.formatPrice(price, currency), - portal.getName())); - helper.performTeleport(event.getPlayer(), event.getTo(), ps, d, portal.getCheckDestinationSafety()); - } - } else { - p.sendMessage(economist.getNSFMessage(currency, - "You need " + economist.formatPrice(price, currency) + " to enter the " + portal.getName() + " portal.")); - } - } else { - // call event for other plugins - MVPortalEvent portalEvent = new MVPortalEvent(d, event.getPlayer(), portal); - this.plugin.getServer().getPluginManager().callEvent(portalEvent); - if (!portalEvent.isCancelled()) { - helper.performTeleport(event.getPlayer(), event.getTo(), ps, d, portal.getCheckDestinationSafety()); - } + // call event for other plugins + MVPortalEvent portalEvent = new MVPortalEvent(d, event.getPlayer(), portal); + this.plugin.getServer().getPluginManager().callEvent(portalEvent); + if (portalEvent.isCancelled()) { + return; + } + if (portalUseResult.needToPay()) { + helper.payPortalEntryFee(portal, player); } + helper.performTeleport(event.getPlayer(), event.getTo(), ps, d, portal.getCheckDestinationSafety()); } } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java new file mode 100644 index 00000000..fc35823d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java @@ -0,0 +1,144 @@ +package org.mvplugins.multiverse.portals.listeners; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerPortalEvent; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.BlockSafety; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.MultiversePortals; +import org.mvplugins.multiverse.portals.PortalPlayerSession; +import org.mvplugins.multiverse.portals.config.PortalsConfig; +import org.mvplugins.multiverse.portals.event.MVPortalEvent; +import org.mvplugins.multiverse.portals.utils.PortalManager; + +@Service +final class MVPPortalListener implements PortalsListener { + + private final PortalManager portalManager; + private final PortalsConfig portalsConfig; + private final BlockSafety blockSafety; + private final WorldManager worldManager; + private final MultiversePortals plugin; + private final PlayerListenerHelper helper; + private final AsyncSafetyTeleporter teleporter; + + @Inject + MVPPortalListener(@NotNull PortalManager portalManager, + @NotNull PortalsConfig portalsConfig, + @NotNull BlockSafety blockSafety, + @NotNull WorldManager worldManager, + @NotNull MultiversePortals plugin, + @NotNull PlayerListenerHelper helper, + @NotNull AsyncSafetyTeleporter teleporter) { + this.portalManager = portalManager; + this.portalsConfig = portalsConfig; + this.blockSafety = blockSafety; + this.worldManager = worldManager; + this.plugin = plugin; + this.helper = helper; + this.teleporter = teleporter; + } + + @EventHandler + public void playerPortal(PlayerPortalEvent event) { + if (event.isCancelled()) { + Logging.fine("This Portal event was already cancelled."); + return; + } + Logging.finer("onPlayerPortal called!"); + Player player = event.getPlayer(); + Location playerPortalLoc = player.getLocation(); + // Determine if we're in a portal + MVPortal portal = portalManager.getPortal(player, playerPortalLoc, false); + // Even if the location was null, we still have to see if + // someone wasn't exactly on (because they can do this). + if (portal == null) { + // Check around the player to make sure + playerPortalLoc = this.blockSafety.findPortalBlockNextTo(event.getFrom()); + if (playerPortalLoc != null) { + Logging.finer("Player was outside of portal, The location has been successfully translated."); + portal = portalManager.getPortal(player, playerPortalLoc, false); + } + } + + if (portal == null) { + return; + } + + Logging.finer("There was a portal found!"); + DestinationInstance portalDest = portal.getDestination(); + if (portalDest == null) { + if (!portalsConfig.getPortalsDefaultToNether()) { + // If portals should not default to the nether, cancel the event + player.sendMessage(String.format( + "This portal %sdoesn't go anywhere. You should exit it now.", ChatColor.RED)); + Logging.fine("Event canceled because this was a MVPortal with an invalid destination. But you had 'portalsdefaulttonether' set to false!"); + event.setCancelled(true); + } + return; + } + + // this is a valid MV Portal, so we'll cancel the event + event.setCancelled(true); + + if (!portal.isFrameValid(playerPortalLoc)) { + player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now."); + return; + } + + Location destLocation = portalDest.getLocation(player).getOrNull(); + if (destLocation == null) { + Logging.fine("Unable to teleport player because destination is null!"); + return; + } + + if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { + Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); + return; + } + + if (portal.getCheckDestinationSafety() && portalDest.checkTeleportSafety()) { + Location safeLocation = blockSafety.findSafeSpawnLocation(portalDest.getLocation(player).getOrNull()); + if (safeLocation == null) { + event.setCancelled(true); + Logging.warning("Portal " + portal.getName() + " destination is not safe!"); + player.sendMessage(ChatColor.RED + "Portal " + portal.getName() + " destination is not safe!"); + return; + } + destLocation = safeLocation; + } + + PortalPlayerSession ps = this.plugin.getPortalSession(player); + if (ps.checkAndSendCooldownMessage()) { + Logging.fine("Player denied teleportation due to cooldown."); + return; + } + PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + if (!portalUseResult.canUse()) { + return; + } + + MVPortalEvent portalEvent = new MVPortalEvent(portalDest, player, portal); + this.plugin.getServer().getPluginManager().callEvent(portalEvent); + + if (portalEvent.isCancelled()) { + Logging.fine("Someone cancelled the MVPlayerPortal Event!"); + return; + } + if (portalUseResult.needToPay()) { + helper.payPortalEntryFee(portal, player); + } + + teleporter.to(destLocation).teleportSingle(player) + .onFailure(() -> Logging.warning("Could not teleport to destination!")); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java index 202f6520..cae53c76 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -11,18 +11,13 @@ import java.util.List; import com.dumptruckman.minecraft.util.Logging; -import org.bukkit.Material; import org.bukkit.event.Listener; import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.economy.MVEconomist; -import org.mvplugins.multiverse.core.permissions.CorePermissionsChecker; -import org.mvplugins.multiverse.core.teleportation.LocationManipulation; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.portals.config.PortalsConfig; import org.mvplugins.multiverse.portals.enums.MoveType; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -36,26 +31,23 @@ import org.mvplugins.multiverse.portals.utils.PortalManager; @Service -public class MVPVehicleListener implements Listener { +public final class MVPVehicleListener implements Listener { private final MultiversePortals plugin; private final PortalManager portalManager; private final AsyncSafetyTeleporter safetyTeleporter; - private final PortalsConfig portalsConfig; - private final MVEconomist economist; + private final PlayerListenerHelper helper; @Inject MVPVehicleListener( @NotNull MultiversePortals plugin, @NotNull PortalManager portalManager, @NotNull AsyncSafetyTeleporter safetyTeleporter, - @NotNull PortalsConfig portalsConfig, - @NotNull MVEconomist economist + @NotNull PlayerListenerHelper helper ) { this.plugin = plugin; this.portalManager = portalManager; this.safetyTeleporter = safetyTeleporter; - this.portalsConfig = portalsConfig; - this.economist = economist; + this.helper = helper; } @EventHandler @@ -64,17 +56,17 @@ public void vehicleMove(VehicleMoveEvent event) { List playerPassengers = new ArrayList<>(); boolean hasNonPlayers = false; for (Entity entity : vehicle.getPassengers()) { - if (entity instanceof Player player) { - PortalPlayerSession ps = this.plugin.getPortalSession(player); - ps.setStaleLocation(vehicle.getLocation(), MoveType.VEHICLE_MOVE); - if (ps.isStaleLocation()) { - Logging.finer("Player %s is stale, not teleporting vehicle", player.getName()); - return; - } - playerPassengers.add(player); - } else { + if (!(entity instanceof Player player)) { hasNonPlayers = true; + continue; } + PortalPlayerSession ps = this.plugin.getPortalSession(player); + ps.setStaleLocation(vehicle.getLocation(), MoveType.VEHICLE_MOVE); + if (ps.isStaleLocation()) { + Logging.finer("Player %s is stale, not teleporting vehicle", player.getName()); + return; + } + playerPassengers.add(player); } MVPortal portal = this.portalManager.getPortal(vehicle.getLocation()); @@ -95,11 +87,15 @@ public void vehicleMove(VehicleMoveEvent event) { } for (Player player : playerPassengers) { - if (!checkPlayerCanUsePortal(portal, player)) { + PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + if (!portalUseResult.canUse()) { Logging.finer("Player %s is not allowed to use portal %s, removing them from the vehicle.", player.getName(), portal.getName()); vehicle.removePassenger(player); } + if (portalUseResult.needToPay()) { + helper.payPortalEntryFee(portal, player); + } } DestinationInstance destination = portal.getDestination(); @@ -112,38 +108,4 @@ public void vehicleMove(VehicleMoveEvent event) { .onFailure(failures -> Logging.finer("Failed to teleport vehicle %s using portal %s. Failures: %s", vehicle.getName(), portal.getName(), failures)); } - - // todo: this logic is duplicated in multiple places - private boolean checkPlayerCanUsePortal(MVPortal portal, Player player) { - // If they're using Access and they don't have permission and they're NOT exempt, return, they're not allowed to tp. - // No longer checking exemption status - if (portalsConfig.getEnforcePortalAccess() && !player.hasPermission(portal.getPermission())) { - - return false; - } - - boolean shouldPay = false; - double price = portal.getPrice(); - Material currency = portal.getCurrency(); - - // Stop the player if the portal costs and they can't pay - if (price != 0D && !player.hasPermission(portal.getExempt())) { - shouldPay = true; - if (price > 0D && !economist.isPlayerWealthyEnough(player, price, currency)) { - player.sendMessage(economist.getNSFMessage(currency, - "You need " + economist.formatPrice(price, currency) + " to enter the " + portal.getName() + " portal.")); - return false; - } - } - - if (shouldPay) { - if (price < 0D) { - economist.deposit(player, -price, currency); - } else { - economist.withdraw(player, price, currency); - } - } - - return true; - } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java index e83288b2..2518e90b 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java @@ -3,23 +3,33 @@ import java.util.Date; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Material; import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.PortalPlayerSession; import org.bukkit.Location; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.portals.config.PortalsConfig; @Service final class PlayerListenerHelper { private final AsyncSafetyTeleporter safetyTeleporter; + private final PortalsConfig portalsConfig; + private final MVEconomist economist; @Inject - PlayerListenerHelper(@NotNull AsyncSafetyTeleporter safetyTeleporter) { + PlayerListenerHelper(@NotNull AsyncSafetyTeleporter safetyTeleporter, + @NotNull PortalsConfig portalsConfig, + @NotNull MVEconomist economist) { this.safetyTeleporter = safetyTeleporter; + this.portalsConfig = portalsConfig; + this.economist = economist; } void stateSuccess(String playerName, String worldName) { @@ -34,6 +44,34 @@ void stateFailure(String playerName, String portalName) { playerName, portalName)); } + PortalUseResult checkPlayerCanUsePortal(MVPortal portal, Player player) { + // If they're using Access and they don't have permission and they're NOT exempt, return, they're not allowed to tp. + // No longer checking exemption status + if (portalsConfig.getEnforcePortalAccess() && !player.hasPermission(portal.getPermission())) { + stateFailure(player.getDisplayName(), portal.getName()); + return PortalUseResult.CANNOT_USE; + } + + double price = portal.getPrice(); + Material currency = portal.getCurrency(); + + // Stop the player if the portal costs and they can't pay + if (price == 0D || player.hasPermission(portal.getExempt())) { + return PortalUseResult.FREE_USE; + } + + if (price > 0D && !economist.isPlayerWealthyEnough(player, price, currency)) { + player.sendMessage(economist.getNSFMessage(currency, + "You need " + economist.formatPrice(price, currency) + " to enter the " + portal.getName() + " portal.")); + return PortalUseResult.CANNOT_USE; + } + return PortalUseResult.PAID_USE; + } + + void payPortalEntryFee(MVPortal portal, Player player) { + economist.payEntryFee(player, portal.getPrice(), portal.getCurrency()); + } + void performTeleport(Player player, Location to, PortalPlayerSession ps, DestinationInstance destination, boolean checkSafety) { safetyTeleporter.to(destination) .checkSafety(checkSafety && destination.checkTeleportSafety()) @@ -48,4 +86,26 @@ void performTeleport(Player player, Location to, PortalPlayerSession ps, Destina player.getDisplayName(), destination, reason) ); } + + enum PortalUseResult { + CANNOT_USE(false, false), + FREE_USE(true, false), + PAID_USE(true, true); + + private final boolean canUse; + private final boolean needToPay; + + PortalUseResult(boolean canUse, boolean needToPay) { + this.canUse = canUse; + this.needToPay = needToPay; + } + + public boolean canUse() { + return canUse; + } + + public boolean needToPay() { + return needToPay; + } + } } From cbf9b8db5f94602189ff226f7468694185469e51 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:53:40 +0800 Subject: [PATCH 02/10] Fix more listener access modifiers --- .../multiverse/portals/listeners/MVPPlayerMoveListener.java | 2 +- .../multiverse/portals/listeners/MVPPortalListener.java | 2 +- .../multiverse/portals/listeners/MVPVehicleListener.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java index 1c623da7..75465b0b 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java @@ -28,7 +28,7 @@ import org.bukkit.event.player.PlayerMoveEvent; @Service -final public class MVPPlayerMoveListener implements Listener { +public final class MVPPlayerMoveListener implements Listener { private final MultiversePortals plugin; private final PortalsConfig portalsConfig; diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java index fc35823d..eb3345bc 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java @@ -49,7 +49,7 @@ final class MVPPortalListener implements PortalsListener { } @EventHandler - public void playerPortal(PlayerPortalEvent event) { + void playerPortal(PlayerPortalEvent event) { if (event.isCancelled()) { Logging.fine("This Portal event was already cancelled."); return; diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java index cae53c76..5865dbe0 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -51,7 +51,7 @@ public final class MVPVehicleListener implements Listener { } @EventHandler - public void vehicleMove(VehicleMoveEvent event) { + void vehicleMove(VehicleMoveEvent event) { Vehicle vehicle = event.getVehicle(); List playerPassengers = new ArrayList<>(); boolean hasNonPlayers = false; From e00eee944c49a926c90923571098868a0a9de7a5 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:02:07 +0800 Subject: [PATCH 03/10] Add support for all living entities to use mv portals --- .../multiverse/portals/MultiversePortals.java | 6 + .../portals/PortalPlayerSession.java | 11 +- .../portals/config/PortalsConfig.java | 10 ++ .../portals/config/PortalsConfigNodes.java | 15 ++- .../listeners/MVPEntityMoveListener.java | 63 +++++++++ .../listeners/MVPEntityPortalListener.java | 52 +++++++ .../listeners/MVPPlayerMoveListener.java | 127 ++++++++++-------- ...ener.java => MVPPlayerPortalListener.java} | 28 ++-- .../portals/listeners/MVPVehicleListener.java | 3 + .../listeners/PlayerListenerHelper.java | 32 ++--- 10 files changed, 249 insertions(+), 98 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java rename src/main/java/org/mvplugins/multiverse/portals/listeners/{MVPPortalListener.java => MVPPlayerPortalListener.java} (86%) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java index 81053d53..135f445e 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java @@ -139,8 +139,14 @@ private void registerEvents() { if (portalsConfigProvider.get().getTeleportVehicles()) { pluginManager.registerEvents(serviceLocator.getService(MVPVehicleListener.class), this); } + if (portalsConfigProvider.get().getTeleportEntities()) { + pluginManager.registerEvents(serviceLocator.getService(MVPEntityPortalListener.class), this); + } if (portalsConfigProvider.get().getUseOnMove()) { pluginManager.registerEvents(serviceLocator.getService(MVPPlayerMoveListener.class), this); + if (portalsConfigProvider.get().getTeleportEntities()) { + pluginManager.registerEvents(serviceLocator.getService(MVPEntityMoveListener.class), this); + } } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java b/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java index b50307f6..658a4c23 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java +++ b/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java @@ -10,6 +10,7 @@ import java.util.Date; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.entity.LivingEntity; import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; @@ -30,7 +31,6 @@ public class PortalPlayerSession { private final PortalManager portalManager; private final WorldManager worldManager; private final DisplayUtils displayUtils; - private final MVEconomist economist; private final Player player; private MVPortal portalSelection = null; @@ -51,7 +51,6 @@ public PortalPlayerSession(MultiversePortals plugin, Player p) { this.portalManager = plugin.getServiceLocator().getService(PortalManager.class); this.worldManager = plugin.getServiceLocator().getService(WorldManager.class); this.displayUtils = plugin.getServiceLocator().getService(DisplayUtils.class); - this.economist = plugin.getServiceLocator().getService(MVEconomist.class); this.player = p; this.setLocation(p.getLocation()); this.lastTeleportTime = new Date(new Date().getTime() - this.portalsConfig.getPortalCooldown()); @@ -107,7 +106,7 @@ private void setStandingInLocation() { } public boolean doTeleportPlayer(MoveType eventType) { - if (eventType == MoveType.PLAYER_MOVE && this.player.isInsideVehicle()) { + if (eventType == MoveType.PLAYER_MOVE && shouldLetVehicleHandle()) { return false; } return this.hasMovedOutOfPortal && this.standingIn != null; @@ -122,7 +121,7 @@ public void setStaleLocation(Location loc, MoveType moveType) { // This should never happen, but seems to when someone gets kicked. return; } - if (this.player.isInsideVehicle() && moveType != MoveType.VEHICLE_MOVE) { + if (shouldLetVehicleHandle() && moveType != MoveType.VEHICLE_MOVE) { return; } // If the player has not moved, they have a stale location @@ -136,6 +135,10 @@ public void setStaleLocation(Location loc, MoveType moveType) { } } + private boolean shouldLetVehicleHandle() { + return this.player.isInsideVehicle() && !(this.player.getVehicle() instanceof LivingEntity); + } + public boolean setLeftClickSelection(Vector v, LoadedMultiverseWorld world) { if(!this.plugin.isWandEnabled()) { return false; diff --git a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java index 57fc206b..aa00d082 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java +++ b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java @@ -295,6 +295,16 @@ public Try setTeleportVehicles(boolean teleportVehicles) { return configHandle.set(configNodes.teleportVehicles, teleportVehicles); } + @ApiStatus.AvailableSince("5.2") + public boolean getTeleportEntities() { + return configHandle.get(configNodes.teleportEntities); + } + + @ApiStatus.AvailableSince("5.2") + public Try setTeleportEntities(boolean teleportEntities) { + return configHandle.set(configNodes.teleportEntities, teleportEntities); + } + /** * * @return diff --git a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfigNodes.java b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfigNodes.java index bbd2e5ba..bfe9ead5 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfigNodes.java @@ -151,18 +151,29 @@ public Object serialize(Material material, Class aClass) { final ConfigNode teleportVehicles = node(ConfigNode.builder("portal-usage.teleport-vehicles", Boolean.class) .comment("") .comment("If enabled, mvportals will teleport all vehicles along with its passengers when the vehicle enters the portal.") - .comment("Vehicles are usually boats, minecarts, pigs and horses.") + .comment("Vehicles are usually boats and minecarts.") + .comment("(NOTE: please turn off the server before changing this config option)") .defaultValue(false) .name("teleport-vehicles") .aliases("teleportvehicles") .onSetValue((oldValue, newValue) -> MultiversePortals.TeleportVehicles = newValue) .build()); + final ConfigNode teleportEntities = node(ConfigNode.builder("portal-usage.teleport-entities", Boolean.class) + .comment("") + .comment("If enabled, all living entities can use the portal if property `teleport-non-players` is true.") + .comment("There may be some performance overhead if your server has a large amount of entities moving around.") + .comment("(NOTE: please turn off the server before changing this config option)") + .defaultValue(false) + .name("teleport-entities") + .build()); + final ConfigNode useOnMove = node(ConfigNode.builder("portal-usage.use-on-move", Boolean.class) .comment("") - .comment("If enabled, player movement will be tracked to determine if the player has entered a portal.") + .comment("If enabled, player movement will be tracked to determine if the player/entity has entered a portal.") .comment("Disabling this will cause mvportals without nether or end fill to not work.") .comment("Only disable this if all your portals have nether or end fill and want to slight enhance performance.") + .comment("(NOTE: please turn off the server before changing this config option)") .defaultValue(true) .name("use-on-move") .aliases("useonmove") diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java new file mode 100644 index 00000000..e2e32b11 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java @@ -0,0 +1,63 @@ +package org.mvplugins.multiverse.portals.listeners; + +import io.papermc.paper.event.entity.EntityMoveEvent; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.PassengerModes; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.config.PortalsConfig; +import org.mvplugins.multiverse.portals.utils.PortalManager; + +@Service +public final class MVPEntityMoveListener implements Listener { + + private final PlayerListenerHelper helper; + private final PortalManager portalManager; + private final AsyncSafetyTeleporter teleporter; + private final PortalsConfig portalsConfig; + + @Inject + MVPEntityMoveListener(@NotNull PlayerListenerHelper helper, + @NotNull PortalManager portalManager, + @NotNull AsyncSafetyTeleporter teleporter, + @NotNull PortalsConfig portalsConfig) { + this.helper = helper; + this.portalManager = portalManager; + this.teleporter = teleporter; + this.portalsConfig = portalsConfig; + } + + @EventHandler(ignoreCancelled = true) + void entityMove(EntityMoveEvent event) { + if (helper.isWithinSameBlock(event.getFrom(), event.getTo())) { + return; + } + + LivingEntity entity = event.getEntity(); + Location location = entity.getLocation(); + + MVPortal portal = portalManager.getPortal(location); + if (portal == null + || !portal.getTeleportNonPlayers() + || (portalsConfig.getNetherAnimation() && !portal.isLegacyPortal())) { + return; + } + + DestinationInstance destination = portal.getDestination(); + if (destination == null) { + return; + } + + teleporter.to(destination) + .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) + .passengerMode(PassengerModes.RETAIN_ALL) + .teleportSingle(entity); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java new file mode 100644 index 00000000..4a896b63 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java @@ -0,0 +1,52 @@ +package org.mvplugins.multiverse.portals.listeners; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEvent; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.PassengerModes; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.utils.PortalManager; + +@Service +public final class MVPEntityPortalListener implements Listener { + + private final PortalManager portalManager; + private final AsyncSafetyTeleporter teleporter; + + @Inject + MVPEntityPortalListener(@NotNull PortalManager portalManager, + @NotNull AsyncSafetyTeleporter teleporter) { + this.portalManager = portalManager; + this.teleporter = teleporter; + } + + @EventHandler(ignoreCancelled = true) + void entityPortal(EntityPortalEvent event) { + Entity entity = event.getEntity(); + Location location = entity.getLocation(); + + MVPortal portal = portalManager.getPortal(location); + if (portal == null || !portal.getTeleportNonPlayers()) { + return; + } + + DestinationInstance destination = portal.getDestination(); + if (destination == null) { + return; + } + + event.setCancelled(true); + + teleporter.to(destination) + .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) + .passengerMode(PassengerModes.RETAIN_ALL) + .teleportSingle(entity); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java index 75465b0b..256b9b83 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java @@ -10,6 +10,8 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.event.Listener; import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -27,6 +29,8 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerMoveEvent; +import java.util.Date; + @Service public final class MVPPlayerMoveListener implements Listener { @@ -34,17 +38,20 @@ public final class MVPPlayerMoveListener implements Listener { private final PortalsConfig portalsConfig; private final PlayerListenerHelper helper; private final WorldManager worldManager; + private final AsyncSafetyTeleporter teleporter; @Inject MVPPlayerMoveListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, @NotNull PlayerListenerHelper helper, - @NotNull WorldManager worldManager) { + @NotNull WorldManager worldManager, + @NotNull AsyncSafetyTeleporter teleporter) { this.plugin = plugin; this.portalsConfig = portalsConfig; this.helper = helper; this.worldManager = worldManager; + this.teleporter = teleporter; } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) @@ -63,58 +70,72 @@ void playerMove(PlayerMoveEvent event) { MVPortal portal = ps.getStandingInPortal(); // If the portal is not null, and it's a legacy portal, // and we didn't show debug info (the debug is meant to toggle), do the stuff. - if (portal != null - && (!portalsConfig.getNetherAnimation() || portal.isLegacyPortal()) - && ps.doTeleportPlayer(MoveType.PLAYER_MOVE) - && !ps.showDebugInfo()) { - - DestinationInstance d = portal.getDestination(); - if (d == null) { - Logging.fine("Invalid Destination!"); - return; - } - player.setFallDistance(0); - - Location destLocation = d.getLocation(player).getOrNull(); - if (destLocation == null) { - Logging.fine("Unable to teleport player because destination is null!"); - return; - } - - if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { - Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); - return; - } - if (!portal.isFrameValid(loc)) { - player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material. You should exit it now."); - return; - } - if (ps.checkAndSendCooldownMessage()) { - return; - } - - PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); - if (!portalUseResult.canUse()) { - return; - } - - // If they're using Access and they don't have permission and they're NOT excempt, return, they're not allowed to tp. - // No longer checking exemption status - if (portalsConfig.getEnforcePortalAccess() && !event.getPlayer().hasPermission(portal.getPermission())) { - this.helper.stateFailure(player.getDisplayName(), portal.getName()); - return; - } - - // call event for other plugins - MVPortalEvent portalEvent = new MVPortalEvent(d, event.getPlayer(), portal); - this.plugin.getServer().getPluginManager().callEvent(portalEvent); - if (portalEvent.isCancelled()) { - return; - } - if (portalUseResult.needToPay()) { - helper.payPortalEntryFee(portal, player); - } - helper.performTeleport(event.getPlayer(), event.getTo(), ps, d, portal.getCheckDestinationSafety()); + if (portal == null + || (portalsConfig.getNetherAnimation() && !portal.isLegacyPortal()) + || !ps.doTeleportPlayer(MoveType.PLAYER_MOVE) + || ps.showDebugInfo()) { + return; + } + + DestinationInstance destination = portal.getDestination(); + if (destination == null) { + Logging.fine("Invalid Destination!"); + return; + } + player.setFallDistance(0); + + Location destLocation = destination.getLocation(player).getOrNull(); + if (destLocation == null) { + Logging.fine("Unable to teleport player because destination is null!"); + return; + } + + if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { + Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); + return; + } + if (!portal.isFrameValid(loc)) { + player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material. You should exit it now."); + return; } + if (ps.checkAndSendCooldownMessage()) { + return; + } + + PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + if (!portalUseResult.canUse()) { + return; + } + + // If they're using Access and they don't have permission and they're NOT excempt, return, they're not allowed to tp. + // No longer checking exemption status + if (portalsConfig.getEnforcePortalAccess() && !event.getPlayer().hasPermission(portal.getPermission())) { + this.helper.stateFailure(player.getDisplayName(), portal.getName()); + return; + } + + // call event for other plugins + MVPortalEvent portalEvent = new MVPortalEvent(destination, event.getPlayer(), portal); + this.plugin.getServer().getPluginManager().callEvent(portalEvent); + if (portalEvent.isCancelled()) { + return; + } + if (portalUseResult.needToPay()) { + helper.payPortalEntryFee(portal, player); + } + + teleporter.to(destination) + .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) + .passengerMode(portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE) + .teleportSingle(player) + .onSuccess(() -> { + ps.playerDidTeleport(destLocation); + ps.setTeleportTime(new Date()); + helper.stateSuccess(player.getDisplayName(), destination.toString()); + }) + .onFailure(reason -> Logging.fine( + "Failed to teleport player '%s' to destination '%s'. Reason: %s", + player.getDisplayName(), destination, reason) + ); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java similarity index 86% rename from src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java rename to src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java index eb3345bc..adf83bff 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java @@ -10,6 +10,7 @@ import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.BlockSafety; +import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -21,7 +22,7 @@ import org.mvplugins.multiverse.portals.utils.PortalManager; @Service -final class MVPPortalListener implements PortalsListener { +final class MVPPlayerPortalListener implements PortalsListener { private final PortalManager portalManager; private final PortalsConfig portalsConfig; @@ -32,13 +33,13 @@ final class MVPPortalListener implements PortalsListener { private final AsyncSafetyTeleporter teleporter; @Inject - MVPPortalListener(@NotNull PortalManager portalManager, - @NotNull PortalsConfig portalsConfig, - @NotNull BlockSafety blockSafety, - @NotNull WorldManager worldManager, - @NotNull MultiversePortals plugin, - @NotNull PlayerListenerHelper helper, - @NotNull AsyncSafetyTeleporter teleporter) { + MVPPlayerPortalListener(@NotNull PortalManager portalManager, + @NotNull PortalsConfig portalsConfig, + @NotNull BlockSafety blockSafety, + @NotNull WorldManager worldManager, + @NotNull MultiversePortals plugin, + @NotNull PlayerListenerHelper helper, + @NotNull AsyncSafetyTeleporter teleporter) { this.portalManager = portalManager; this.portalsConfig = portalsConfig; this.blockSafety = blockSafety; @@ -48,12 +49,8 @@ final class MVPPortalListener implements PortalsListener { this.teleporter = teleporter; } - @EventHandler + @EventHandler(ignoreCancelled = true) void playerPortal(PlayerPortalEvent event) { - if (event.isCancelled()) { - Logging.fine("This Portal event was already cancelled."); - return; - } Logging.finer("onPlayerPortal called!"); Player player = event.getPlayer(); Location playerPortalLoc = player.getLocation(); @@ -109,7 +106,6 @@ void playerPortal(PlayerPortalEvent event) { if (portal.getCheckDestinationSafety() && portalDest.checkTeleportSafety()) { Location safeLocation = blockSafety.findSafeSpawnLocation(portalDest.getLocation(player).getOrNull()); if (safeLocation == null) { - event.setCancelled(true); Logging.warning("Portal " + portal.getName() + " destination is not safe!"); player.sendMessage(ChatColor.RED + "Portal " + portal.getName() + " destination is not safe!"); return; @@ -138,7 +134,9 @@ void playerPortal(PlayerPortalEvent event) { helper.payPortalEntryFee(portal, player); } - teleporter.to(destLocation).teleportSingle(player) + teleporter.to(destLocation) + .passengerMode(portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE) + .teleportSingle(player) .onFailure(() -> Logging.warning("Could not teleport to destination!")); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java index 5865dbe0..e353daff 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -52,6 +52,9 @@ public final class MVPVehicleListener implements Listener { @EventHandler void vehicleMove(VehicleMoveEvent event) { + if (helper.isWithinSameBlock(event.getFrom(), event.getTo())) { + return; + } Vehicle vehicle = event.getVehicle(); List playerPassengers = new ArrayList<>(); boolean hasNonPlayers = false; diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java index 2518e90b..fbecbb28 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java @@ -1,17 +1,12 @@ package org.mvplugins.multiverse.portals.listeners; -import java.util.Date; - import com.dumptruckman.minecraft.util.Logging; import org.bukkit.Material; -import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.economy.MVEconomist; -import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.portals.MVPortal; -import org.mvplugins.multiverse.portals.PortalPlayerSession; import org.bukkit.Location; import org.bukkit.entity.Player; import org.mvplugins.multiverse.portals.config.PortalsConfig; @@ -19,19 +14,23 @@ @Service final class PlayerListenerHelper { - private final AsyncSafetyTeleporter safetyTeleporter; private final PortalsConfig portalsConfig; private final MVEconomist economist; @Inject - PlayerListenerHelper(@NotNull AsyncSafetyTeleporter safetyTeleporter, - @NotNull PortalsConfig portalsConfig, + PlayerListenerHelper(@NotNull PortalsConfig portalsConfig, @NotNull MVEconomist economist) { - this.safetyTeleporter = safetyTeleporter; this.portalsConfig = portalsConfig; this.economist = economist; } + boolean isWithinSameBlock(Location from, Location to) { + return from.getWorld() == to.getWorld() + && from.getBlockX() == to.getBlockX() + && from.getBlockY() == to.getBlockY() + && from.getBlockZ() == to.getBlockZ(); + } + void stateSuccess(String playerName, String worldName) { Logging.fine(String.format( "MV-Portals is allowing Player '%s' to use the portal '%s'.", @@ -72,21 +71,6 @@ void payPortalEntryFee(MVPortal portal, Player player) { economist.payEntryFee(player, portal.getPrice(), portal.getCurrency()); } - void performTeleport(Player player, Location to, PortalPlayerSession ps, DestinationInstance destination, boolean checkSafety) { - safetyTeleporter.to(destination) - .checkSafety(checkSafety && destination.checkTeleportSafety()) - .teleportSingle(player) - .onSuccess(() -> { - ps.playerDidTeleport(to); - ps.setTeleportTime(new Date()); - this.stateSuccess(player.getDisplayName(), destination.toString()); - }) - .onFailure(reason -> Logging.fine( - "Failed to teleport player '%s' to destination '%s'. Reason: %s", - player.getDisplayName(), destination, reason) - ); - } - enum PortalUseResult { CANNOT_USE(false, false), FREE_USE(true, false), From b95ba1ef52a88ea25ed6e155b9b615af5130e2df Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:05:18 +0800 Subject: [PATCH 04/10] Fix spacing --- .../org/mvplugins/multiverse/portals/config/PortalsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java index aa00d082..de49de96 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java +++ b/src/main/java/org/mvplugins/multiverse/portals/config/PortalsConfig.java @@ -301,7 +301,7 @@ public boolean getTeleportEntities() { } @ApiStatus.AvailableSince("5.2") - public Try setTeleportEntities(boolean teleportEntities) { + public Try setTeleportEntities(boolean teleportEntities) { return configHandle.set(configNodes.teleportEntities, teleportEntities); } From 0a8b07dac5bafd1f834bca580b007bd45cf52ff7 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:00:42 +0800 Subject: [PATCH 05/10] Implement action handler to support command execution and bungeecord proxy --- build.gradle | 7 + .../multiverse/portals/MVPortal.java | 120 +++++++++++++++--- .../multiverse/portals/MVPortalNodes.java | 23 +++- .../multiverse/portals/MultiversePortals.java | 16 +++ .../portals/action/ActionFailureReason.java | 7 + .../portals/action/ActionHandler.java | 20 +++ .../portals/action/ActionHandlerProvider.java | 35 +++++ .../portals/action/ActionHandlerType.java | 30 +++++ .../action/types/CommandActionHandler.java | 23 ++++ .../types/CommandActionHandlerType.java | 30 +++++ .../portals/action/types/CommandRunner.java | 90 +++++++++++++ .../MultiverseDestinationActionHandler.java | 47 +++++++ ...ultiverseDestinationActionHandlerType.java | 41 ++++++ .../action/types/ServerActionHandler.java | 44 +++++++ .../action/types/ServerActionHandlerType.java | 37 ++++++ .../portals/commands/CreateCommand.java | 4 +- .../portals/commands/ListCommand.java | 50 ++------ .../portals/commands/ModifyCommand.java | 3 +- .../portals/event/MVPortalEvent.java | 7 +- .../portals/listeners/MVPCoreListener.java | 2 +- .../listeners/MVPEntityMoveListener.java | 14 +- .../listeners/MVPEntityPortalListener.java | 40 ++++-- .../listeners/MVPPlayerMoveListener.java | 40 +----- .../listeners/MVPPlayerPortalListener.java | 50 +------- .../portals/listeners/MVPVehicleListener.java | 14 +- .../portals/utils/BungeeServerList.java | 70 ++++++++++ .../portals/utils/DisplayUtils.java | 82 +++++++----- src/main/resources/plugin.yml | 2 +- 28 files changed, 727 insertions(+), 221 deletions(-) create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java create mode 100644 src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java diff --git a/build.gradle b/build.gradle index eebbb6bc..be9e5e7d 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,10 @@ repositories { name = 'benthecat' url = uri('https://repo.c0ding.party/multiverse-beta') } + maven { + name = "helpchatRepoReleases" + url = uri("https://repo.helpch.at/releases/") + } } configure(apiDependencies) { @@ -32,6 +36,9 @@ dependencies { exclude group: 'org.bukkit', module: 'bukkit' } + // PlaceholderAPI + externalPlugin 'me.clip:placeholderapi:2.11.6' + // Utils shadowed('com.dumptruckman.minecraft:Logging:1.1.1') { exclude group: 'junit', module: 'junit' diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java index 21deb35f..db27eada 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java @@ -9,16 +9,20 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.Stack; import java.util.regex.Pattern; import com.dumptruckman.minecraft.util.Logging; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle; import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; @@ -27,9 +31,15 @@ import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.teleportation.BlockSafety; +import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.vavr.control.Option; import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandler; +import org.mvplugins.multiverse.portals.action.ActionHandlerProvider; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; import org.mvplugins.multiverse.portals.config.PortalsConfig; import org.mvplugins.multiverse.portals.enums.PortalType; import org.bukkit.Location; @@ -62,6 +72,8 @@ public static MVPortal loadMVPortalFromConfig(MultiversePortals instance, String private final WorldManager worldManager; private final DestinationsProvider destinationsProvider; private final BlockSafety blockSafety; + private final ActionHandlerProvider actionHandlerProvider; + private final MVCommandManager commandManager; private final String name; private final MVPortalNodes configNodes; @@ -94,16 +106,24 @@ private MVPortal(MultiversePortals plugin, String name) { this.worldManager = this.plugin.getServiceLocator().getService(WorldManager.class); this.destinationsProvider = this.plugin.getServiceLocator().getService(DestinationsProvider.class); this.blockSafety = this.plugin.getServiceLocator().getService(BlockSafety.class); + this.actionHandlerProvider = this.plugin.getServiceLocator().getService(ActionHandlerProvider.class); + this.commandManager = this.plugin.getServiceLocator().getService(MVCommandManager.class); this.name = name; var config = this.plugin.getPortalsConfig(); this.configNodes = new MVPortalNodes(plugin, this); - var portalSection = config.getConfigurationSection("portals." + this.name); - if (portalSection == null) { - portalSection = config.createSection("portals." + this.name); - } - this.configHandle = MemoryConfigurationHandle.builder(portalSection, configNodes.getNodes()) + var portalSection = Option.of(config.getConfigurationSection("portals." + this.name)) + .getOrElse(() -> config.createSection("portals." + this.name)); + this.configHandle = setUpConfigHandle(portalSection); + this.stringPropertyHandle = new StringPropertyHandle(this.configHandle); + configHandle.load(); + + setUpPermissions(); + } + + private MemoryConfigurationHandle setUpConfigHandle(ConfigurationSection portalSection) { + return MemoryConfigurationHandle.builder(portalSection, configNodes.getNodes()) .migrator(ConfigMigrator.builder(configNodes.version) .addVersionMigrator(VersionMigrator.builder(1.0) .addAction(MoveMigratorAction.of("safeteleport", "safe-teleport")) @@ -119,11 +139,14 @@ private MVPortal(MultiversePortals plugin, String name) { } }) .build()) + .addVersionMigrator(VersionMigrator.builder(1.2) + .addAction(MoveMigratorAction.of("destination", "action")) + .build()) .build()) .build(); - this.stringPropertyHandle = new StringPropertyHandle(this.configHandle); - configHandle.load(); + } + private void setUpPermissions() { this.permission = this.plugin.getServer().getPluginManager().getPermission("multiverse.portal.access." + this.name); if (this.permission == null) { this.permission = new Permission("multiverse.portal.access." + this.name, "Allows access to the " + this.name + " portal", PermissionDefault.OP); @@ -142,6 +165,10 @@ private MVPortal(MultiversePortals plugin, String name) { } } + /** + * + * @return + */ public String getName() { return this.name; } @@ -271,24 +298,41 @@ public PortalLocation getPortalLocation() { return this.location; } - public boolean setDestination(String destinationString) { - DestinationInstance newDestination = this.destinationsProvider.parseDestination(destinationString).getOrNull(); - return setDestination(newDestination); + public Try setActionType(ActionHandlerType actionType) { + return configHandle.set(configNodes.actionType, actionType.getName()); } - public boolean setDestination(DestinationInstance newDestination) { - if (newDestination == null) { - Logging.warning("Portal " + this.name + " has an invalid DESTINATION!"); - return false; - } - return this.configHandle.set(configNodes.destination, newDestination.toString()).isSuccess(); + public Try setActionType(String actionType) { + return configHandle.set(configNodes.actionType, actionType); } - public DestinationInstance getDestination() { - return this.destinationsProvider.parseDestination(this.configHandle.get(configNodes.destination)) - .onFailure(f -> - Logging.warning("Portal " + this.name + " has an invalid DESTINATION! " + f.getFailureMessage().formatted())) - .getOrNull(); + public String getActionType() { + return this.configHandle.get(configNodes.actionType); + } + + public Try setAction(String action) { + return configHandle.set(configNodes.action, action); + } + + public String getAction() { + return configHandle.get(configNodes.action); + } + + public Attempt, ActionFailureReason> getActionHandler() { + return actionHandlerProvider.parseHandler(getActionType(), getAction()); + } + + public Attempt runActionFor(Entity entity) { + return getActionHandler() + .mapAttempt(actionHandler -> actionHandler.runAction(this, entity)) + .onSuccess(() -> { + if (entity instanceof Player player) { + plugin.getPortalSession(player).setTeleportTime(new Date()); + } + }) + .onFailure(failure -> { + Logging.warning(failure.getFailureMessage().formatted(commandManager.getCommandIssuer(entity))); + }); } /** @@ -598,4 +642,38 @@ public boolean isExempt(Player player) { public PortalLocation getLocation() { return getPortalLocation(); } + + + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") + public boolean setDestination(String destinationString) { + DestinationInstance newDestination = this.destinationsProvider.parseDestination(destinationString).getOrNull(); + return setDestination(newDestination); + } + + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") + public boolean setDestination(DestinationInstance newDestination) { + if (newDestination == null) { + Logging.warning("Portal " + this.name + " has an invalid DESTINATION!"); + return false; + } + if (!Objects.equals(getActionType(), "multiverse-destination")) { + Logging.warning("Portal " + this.name + " is not set to use multiverse destination!"); + return false; + } + return this.configHandle.set(configNodes.action, newDestination.toString()).isSuccess(); + } + + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") + public DestinationInstance getDestination() { + return this.destinationsProvider.parseDestination(getAction()) + .onFailure(f -> { + if (getAction().equals("multiverse-destination")) { + Logging.warning("Portal " + this.name + " has an invalid DESTINATION! " + f.getFailureMessage().formatted()); + } + }) + .getOrNull(); + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java index 4607b864..f3098eef 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java @@ -6,10 +6,13 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode; import org.mvplugins.multiverse.core.config.node.Node; import org.mvplugins.multiverse.core.config.node.NodeGroup; -import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.exceptions.MultiverseException; import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.portals.action.ActionHandler; +import org.mvplugins.multiverse.portals.action.ActionHandlerProvider; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; import org.mvplugins.multiverse.portals.utils.MultiverseRegion; import java.util.Collections; @@ -22,11 +25,13 @@ final class MVPortalNodes { private MultiversePortals plugin; private MVPortal portal; private DestinationsProvider destinationsProvider; + private ActionHandlerProvider actionHandlerProvider; MVPortalNodes(MultiversePortals plugin, MVPortal portal) { this.plugin = plugin; this.portal = portal; this.destinationsProvider = MultiverseCoreApi.get().getDestinationsProvider(); + this.actionHandlerProvider = plugin.getServiceLocator().getService(ActionHandlerProvider.class); } NodeGroup getNodes() { @@ -90,13 +95,17 @@ private N node(N node) { .onSetValue((oldValue, newValue) -> portal.setPortalLocationInternal(PortalLocation.parseLocation(newValue))) .build()); - final ConfigNode destination = node(ConfigNode.builder("destination", String.class) + final ConfigNode actionType = node(ConfigNode.builder("action-type", String.class) + .suggester(input -> actionHandlerProvider.getAllHandlerTypes()) + .defaultValue("multiverse-destination") + .build()); + + final ConfigNode action = node(ConfigNode.builder("action", String.class) .defaultValue("") - .aliases("dest") - .suggester((sender, input) -> destinationsProvider.suggestDestinationStrings(sender, input)) - .stringParser((sender, input, type) -> destinationsProvider.parseDestination(sender, input) - .map(DestinationInstance::toString) - .toTry()) + .aliases("destination", "dest") + .suggester((sender, input) -> actionHandlerProvider.getHandlerType(portal.getActionType()) + .map(actionHandlerType -> actionHandlerType.suggestActions(sender, input)) + .getOrElse(Collections.emptyList())) .build()); final ConfigNode checkDestinationSafety = node(ConfigNode.builder("check-destination-safety", Boolean.class) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java index 135f445e..92b642a9 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java @@ -16,6 +16,8 @@ import java.util.logging.Level; import com.dumptruckman.minecraft.util.Logging; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.core.config.CoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; @@ -26,6 +28,8 @@ import org.mvplugins.multiverse.external.jakarta.inject.Provider; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.portals.action.ActionHandlerProvider; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; import org.mvplugins.multiverse.portals.commands.PortalsCommand; import org.mvplugins.multiverse.portals.command.PortalsCommandCompletions; import org.mvplugins.multiverse.portals.command.PortalsCommandContexts; @@ -70,6 +74,8 @@ public class MultiversePortals extends MultiverseModule { private Provider portalsConfigProvider; @Inject private Provider metricsConfiguratorProvider; + @Inject + private Provider actionHandlerProvider; private FileConfiguration MVPPortalConfig; private WorldEditConnection worldEditConnection; @@ -104,6 +110,7 @@ public void onEnable() { this.getServer().getPluginManager().disablePlugin(this); return; } + this.setUpActionHandlers(); this.loadPortals(); this.setupMetrics(); @@ -112,6 +119,9 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new WorldEditPluginListener(), this); MultiversePortalsApi.init(this); + // for teleporting between servers + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + Logging.log(true, Level.INFO, " Enabled - By %s", StringFormatter.joinAnd(getDescription().getAuthors())); } @@ -180,6 +190,12 @@ public void destroyPortalSession(Player p) { this.portalSessions.remove(p.getName()); } + private void setUpActionHandlers() { + serviceLocator.getAllServices(ActionHandlerType.class).forEach(handler -> { + actionHandlerProvider.get().registerHandlerType(handler); + }); + } + private void loadPortals() { this.MVPPortalConfig = YamlConfiguration.loadConfiguration(new File(getDataFolder(), "portals.yml")); if (!this.MVPPortalConfig.isConfigurationSection("portals")) { diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java new file mode 100644 index 00000000..1aaa7332 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java @@ -0,0 +1,7 @@ +package org.mvplugins.multiverse.portals.action; + +import org.mvplugins.multiverse.core.utils.result.FailureReason; + +public interface ActionFailureReason extends FailureReason { + ActionFailureReason INSTANCE = new ActionFailureReason() {}; +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java new file mode 100644 index 00000000..792273f7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java @@ -0,0 +1,20 @@ +package org.mvplugins.multiverse.portals.action; + +import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.portals.MVPortal; + +public abstract class ActionHandler, H extends ActionHandler> { + + private final T handlerType; + + protected ActionHandler(T handlerType) { + this.handlerType = handlerType; + } + + public T getHandlerType() { + return handlerType; + } + + public abstract Attempt runAction(MVPortal portal, Entity entity); +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java new file mode 100644 index 00000000..3560331a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java @@ -0,0 +1,35 @@ +package org.mvplugins.multiverse.portals.action; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.vavr.control.Option; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Service +public final class ActionHandlerProvider { + + private final Map> handlerTypeMap = new HashMap<>(); + + public void registerHandlerType(ActionHandlerType handlerType) { + handlerTypeMap.put(handlerType.getName(), handlerType); + } + + public Collection getAllHandlerTypes() { + return handlerTypeMap.keySet(); + } + + public Attempt, ActionFailureReason> getHandlerType(String name) { + return Option.of(handlerTypeMap.get(name)) + .map(Attempt::, ActionFailureReason>success) + .getOrElse(() -> Attempt.failure(ActionFailureReason.INSTANCE, Message.of("Unknown action type: " + name))); + } + + public Attempt, ActionFailureReason> parseHandler(String actionType, String action) { + return getHandlerType(actionType) + .mapAttempt(actionHandlerType -> actionHandlerType.parseHandler(action)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java new file mode 100644 index 00000000..7a533296 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.portals.action; + +import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; + +import java.util.Collection; +import java.util.Collections; + +@Contract +public abstract class ActionHandlerType, H extends ActionHandler> { + + private final String name; + + protected ActionHandlerType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public abstract Attempt parseHandler(String action); + + public @NotNull Collection suggestActions(CommandSender sender, String input) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java new file mode 100644 index 00000000..1364aafc --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java @@ -0,0 +1,23 @@ +package org.mvplugins.multiverse.portals.action.types; + +import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandler; + +final class CommandActionHandler extends ActionHandler { + + private final CommandRunner commandRunner; + + CommandActionHandler(CommandActionHandlerType handlerType, CommandRunner commandRunner) { + super(handlerType); + this.commandRunner = commandRunner; + } + + @Override + public Attempt runAction(MVPortal portal, Entity entity) { + commandRunner.runCommand(entity); + return Attempt.success(null); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java new file mode 100644 index 00000000..1a7f3d94 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.portals.action.types; + +import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; + +import java.util.Collection; + +@Service +final class CommandActionHandlerType extends ActionHandlerType { + + @Inject + CommandActionHandlerType() { + super("command"); + } + + @Override + public Attempt parseHandler(String action) { + return Attempt.success(new CommandActionHandler(this, CommandRunner.fromString(action))); + } + + @Override + public Collection suggestActions(CommandSender sender, String input) { + //todo possibly use command map to suggest tab completion + return super.suggestActions(sender, input); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java new file mode 100644 index 00000000..7d55e26f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java @@ -0,0 +1,90 @@ +package org.mvplugins.multiverse.portals.action.types; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.utils.REPatterns; + +abstract class CommandRunner { + + static CommandRunner fromString(String command) { + String[] split = REPatterns.COLON.split(command, 2); + return switch ((split.length == 2) ? split[0] : "") { + case "op" -> new Op(split[1]); + case "console" -> new Console(split[1]); + default -> new Self(command); + }; + } + + private final String cmdStr; + + CommandRunner(String cmdStr) { + this.cmdStr = cmdStr; + } + + void runCommand(CommandSender sender) { + runCommand(sender, parseCmdStr(sender)); + } + + private String parseCmdStr(CommandSender sender) { + String parsedCmd = cmdStr; + if (sender instanceof Entity entity) { + parsedCmd = parsedCmd.replace("%world%", entity.getWorld().getName()); + } + if (sender instanceof OfflinePlayer player) { + parsedCmd = parsedCmd.replace("%player%", String.valueOf(player.getName())); + if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + parsedCmd = PlaceholderAPI.setPlaceholders(player, parsedCmd); + } + } + return parsedCmd; + } + + protected abstract void runCommand(CommandSender sender, String cmd); + + private static class Self extends CommandRunner { + + private Self(String cmdStr) { + super(cmdStr); + } + + @Override + protected void runCommand(CommandSender sender, String cmd) { + Bukkit.dispatchCommand(sender, cmd); + } + } + + private static class Op extends CommandRunner { + + private Op(String cmdStr) { + super(cmdStr); + } + + @Override + protected void runCommand(CommandSender sender, String cmd) { + boolean shouldRemoveOp = false; + if (!sender.isOp()) { + sender.setOp(true); + shouldRemoveOp = true; + } + Bukkit.dispatchCommand(sender, cmd); + if (shouldRemoveOp) { + sender.setOp(false); + } + } + } + + private static class Console extends CommandRunner { + + private Console(String cmdStr) { + super(cmdStr); + } + + @Override + protected void runCommand(CommandSender sender, String cmd) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java new file mode 100644 index 00000000..565d2bb3 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.portals.action.types; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.PassengerMode; +import org.mvplugins.multiverse.core.teleportation.PassengerModes; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandler; + +final class MultiverseDestinationActionHandler extends ActionHandler { + + private final AsyncSafetyTeleporter teleporter; + private final DestinationInstance destinationInstance; + + MultiverseDestinationActionHandler(MultiverseDestinationActionHandlerType handlerType, + AsyncSafetyTeleporter teleporter, + DestinationInstance destinationInstance) { + super(handlerType); + this.teleporter = teleporter; + this.destinationInstance = destinationInstance; + } + + @Override + public Attempt runAction(MVPortal portal, Entity entity) { + teleporter.to(destinationInstance) + .checkSafety(portal.getCheckDestinationSafety() && destinationInstance.checkTeleportSafety()) + .passengerMode(passengerModeFor(portal, entity)) + .teleportSingle(entity) + .onFailure(reason -> Logging.warning( + "Failed to teleport entity '%s' to destination '%s'. Reason: %s", + entity.getName(), destinationInstance, reason) + ); + return Attempt.success(null); + } + + private PassengerMode passengerModeFor(MVPortal portal, Entity entity) { + if (entity instanceof Vehicle) { + return PassengerModes.RETAIN_ALL; + } + return portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java new file mode 100644 index 00000000..d8e68d8a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java @@ -0,0 +1,41 @@ +package org.mvplugins.multiverse.portals.action.types; + +import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; + +import java.util.Collection; + +@Service +final class MultiverseDestinationActionHandlerType extends ActionHandlerType { + + private final DestinationsProvider destinationsProvider; + private final AsyncSafetyTeleporter teleporter; + + @Inject + MultiverseDestinationActionHandlerType(@NotNull DestinationsProvider destinationsProvider, + @NotNull AsyncSafetyTeleporter teleporter) { + super("multiverse-destination"); + this.destinationsProvider = destinationsProvider; + this.teleporter = teleporter; + } + + @Override + public Attempt parseHandler(String action) { + return destinationsProvider.parseDestination(action) + .transform(ActionFailureReason.INSTANCE) + .map(destinationInstance -> + new MultiverseDestinationActionHandler(this, teleporter, destinationInstance)); + } + + @Override + public Collection suggestActions(CommandSender sender, String input) { + return destinationsProvider.suggestDestinationStrings(sender, input); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java new file mode 100644 index 00000000..d6b86904 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java @@ -0,0 +1,44 @@ +package org.mvplugins.multiverse.portals.action.types; + +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.MultiversePortals; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandler; + +final class ServerActionHandler extends ActionHandler { + + private final MultiversePortals plugin; + private final String serverName; + + ServerActionHandler(ServerActionHandlerType handlerType, MultiversePortals plugin, String serverName) { + super(handlerType); + this.plugin = plugin; + this.serverName = serverName; + } + + @Override + public Attempt runAction(MVPortal portal, Entity entity) { + if (!(entity instanceof Player player)) { + return Attempt.failure(ActionFailureReason.INSTANCE, Message.of("Only players can teleport between servers!")); + } + return Try + .run(() -> { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Connect"); + out.writeUTF(serverName); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + }) + .map(Attempt::success) + .recover(ex -> Attempt.failure(ActionFailureReason.INSTANCE, + Message.of("An error occurred while sending plugin message to proxy: " + ex.getLocalizedMessage()))) + .getOrElse(() -> Attempt.failure(ActionFailureReason.INSTANCE, + Message.of("An unknown error occurred while sending plugin message to proxy."))); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java new file mode 100644 index 00000000..e8769b68 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java @@ -0,0 +1,37 @@ +package org.mvplugins.multiverse.portals.action.types; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.portals.MultiversePortals; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; +import org.mvplugins.multiverse.portals.utils.BungeeServerList; + +import java.util.Collection; + +@Service +final class ServerActionHandlerType extends ActionHandlerType { + + private final MultiversePortals plugin; + private final BungeeServerList bungeeServerList; + + @Inject + ServerActionHandlerType(@NotNull MultiversePortals plugin, @NotNull BungeeServerList bungeeServerList) { + super("server"); + this.plugin = plugin; + this.bungeeServerList = bungeeServerList; + } + + @Override + public Attempt parseHandler(String action) { + return Attempt.success(new ServerActionHandler(this, plugin, action)); + } + + @Override + public Collection suggestActions(CommandSender sender, String input) { + return bungeeServerList.getServerNames(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/commands/CreateCommand.java b/src/main/java/org/mvplugins/multiverse/portals/commands/CreateCommand.java index ca228786..901c1293 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/commands/CreateCommand.java +++ b/src/main/java/org/mvplugins/multiverse/portals/commands/CreateCommand.java @@ -82,10 +82,10 @@ void onCreateCommand( ps.selectPortal(portal); if (destination != null) { - portal.setDestination(destination); + portal.setAction(destination.toString()); this.plugin.savePortalsConfig(); } else { - player.sendMessage(ChatColor.RED + "Portal destination not set. Use " + ChatColor.DARK_AQUA + "/mvp modify destination " + ChatColor.RED + " to set one."); + player.sendMessage(ChatColor.RED + "Portal action not set. Use " + ChatColor.DARK_AQUA + "/mvp modify action " + ChatColor.RED + " to set one."); } // todo: Automatically get exact destination from player location diff --git a/src/main/java/org/mvplugins/multiverse/portals/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/portals/commands/ListCommand.java index 9b02c449..e0bc64d8 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/commands/ListCommand.java +++ b/src/main/java/org/mvplugins/multiverse/portals/commands/ListCommand.java @@ -5,7 +5,6 @@ import org.mvplugins.multiverse.core.command.LegacyAliasCommand; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; -import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; @@ -18,6 +17,7 @@ import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.utils.DisplayUtils; import org.mvplugins.multiverse.portals.utils.PortalManager; import java.util.ArrayList; @@ -30,11 +30,13 @@ class ListCommand extends PortalsCommand { private final PortalManager portalManager; private final WorldManager worldManager; + private final DisplayUtils displayUtils; @Inject - ListCommand(@NotNull PortalManager portalManager, @NotNull WorldManager worldManager) { + ListCommand(@NotNull PortalManager portalManager, @NotNull WorldManager worldManager, @NotNull DisplayUtils displayUtils) { this.portalManager = portalManager; this.worldManager = worldManager; + this.displayUtils = displayUtils; } @Subcommand("list") @@ -91,43 +93,9 @@ private List getPortals(CommandSender sender, MultiverseWorld world, Str filter = ""; } for (MVPortal portal : (world == null) ? this.portalManager.getPortals(sender) : this.portalManager.getPortals(sender, world)) { - String destination = ""; - if (portal.getDestination() != null) { - destination = portal.getDestination().toString(); - String destType = portal.getDestination().getIdentifier(); - if (destType.equals("w")) { - MultiverseWorld destWorld = this.worldManager.getLoadedWorld(destination).getOrNull(); - if (destWorld != null) { - destination = "(World) " + ChatColor.DARK_AQUA + destination; - } - } - if (destType.equals("p")) { - // todo: I think should use instance check instead of destType prefix - // String targetWorldName = this.portalManager.getPortal(portal.getDestination().getName()).getWorld().getName(); - // destination = "(Portal) " + ChatColor.DARK_AQUA + portal.getDestination().getName() + ChatColor.GRAY + " (" + targetWorldName + ")"; - } - if (destType.equals("e")) { - String destinationWorld = portal.getDestination().toString().split(":")[1]; - String destPart = portal.getDestination().toString().split(":")[2]; - String[] locParts = destPart.split(","); - int x, y, z; - try { - x = (int) Double.parseDouble(locParts[0]); - y = (int) Double.parseDouble(locParts[1]); - z = (int) Double.parseDouble(locParts[2]); - } catch (NumberFormatException e) { - e.printStackTrace(); - continue; - } - if (destType.equals("i")) { - destination = ChatColor.RED + "Invalid destination"; - } - destination = "(Location) " + ChatColor.DARK_AQUA + destinationWorld + ", " + x + ", " + y + ", " + z; - } - } - - if (portal.getName().toLowerCase().contains(filter.toLowerCase()) || (portal.getDestination() != null && destination.toLowerCase().contains(filter.toLowerCase()))) { - portals.add(ChatColor.YELLOW + portal.getName() + ((portal.getDestination() != null) ? (ChatColor.AQUA + " -> " + ChatColor.GOLD + destination) : "")); + String destination = displayUtils.formatActionAsMVDestination(portal); + if (portal.getName().toLowerCase().contains(filter.toLowerCase()) || destination.toLowerCase().contains(filter.toLowerCase())) { + portals.add(ChatColor.YELLOW + portal.getName() + ChatColor.AQUA + " -> " + ChatColor.GOLD + destination); } } java.util.Collections.sort(portals); @@ -147,8 +115,8 @@ private List getPortals(CommandSender sender, MultiverseWorld world, Str @Service private final static class LegacyAlias extends ListCommand implements LegacyAliasCommand { @Inject - LegacyAlias(PortalManager portalManager, WorldManager worldManager) { - super(portalManager, worldManager); + LegacyAlias(PortalManager portalManager, WorldManager worldManager, DisplayUtils displayUtils) { + super(portalManager, worldManager, displayUtils); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java index b6ea5697..cab34510 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java @@ -9,6 +9,7 @@ import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.ConsumesRest; import org.mvplugins.multiverse.external.acf.commands.annotation.Description; import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; import org.mvplugins.multiverse.external.acf.commands.annotation.Single; @@ -49,7 +50,7 @@ public void onModifyCommand( @Description("The property to modify.") String property, - @Single + @ConsumesRest @Syntax("") @Description("The value to set.") String value diff --git a/src/main/java/org/mvplugins/multiverse/portals/event/MVPortalEvent.java b/src/main/java/org/mvplugins/multiverse/portals/event/MVPortalEvent.java index cd621975..12e41e4a 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/event/MVPortalEvent.java +++ b/src/main/java/org/mvplugins/multiverse/portals/event/MVPortalEvent.java @@ -12,6 +12,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.enums.PortalType; @@ -76,8 +77,12 @@ public Location getFrom() { /** * Returns the destination that the player will spawn at. * - * @return The destination the player will spawn at. + * @return The destination the player will spawn at. May be null. + * + * @deprecated Portals can have different types of actions, not just destinations. See {@link org.mvplugins.multiverse.portals.action.ActionHandlerProvider} */ + @Deprecated(since = "5.2", forRemoval = true) + @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public DestinationInstance getDestination() { return this.destination; } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java index ade92c39..be4dc81b 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPCoreListener.java @@ -89,7 +89,7 @@ public void portalTouchEvent(MVPlayerTouchedPortalEvent event) { // The player can't see this portal, and can't use it. Logging.finer(String.format("'%s' was DENIED access to this portal event.", event.getPlayer().getName())); event.setCanUseThisPortal(false); - } else if (p.getDestination() == null) { + } else if (p.getActionHandler().isFailure()) { if (config.getPortalsDefaultToNether()) { Logging.finer("Allowing MVPortal to act as nether portal."); return; diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java index e2e32b11..2a931734 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.portals.listeners; +import com.dumptruckman.minecraft.util.Logging; import io.papermc.paper.event.entity.EntityMoveEvent; import org.bukkit.Location; import org.bukkit.entity.LivingEntity; @@ -20,17 +21,14 @@ public final class MVPEntityMoveListener implements Listener { private final PlayerListenerHelper helper; private final PortalManager portalManager; - private final AsyncSafetyTeleporter teleporter; private final PortalsConfig portalsConfig; @Inject MVPEntityMoveListener(@NotNull PlayerListenerHelper helper, @NotNull PortalManager portalManager, - @NotNull AsyncSafetyTeleporter teleporter, @NotNull PortalsConfig portalsConfig) { this.helper = helper; this.portalManager = portalManager; - this.teleporter = teleporter; this.portalsConfig = portalsConfig; } @@ -50,14 +48,6 @@ void entityMove(EntityMoveEvent event) { return; } - DestinationInstance destination = portal.getDestination(); - if (destination == null) { - return; - } - - teleporter.to(destination) - .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) - .passengerMode(PassengerModes.RETAIN_ALL) - .teleportSingle(entity); + portal.runActionFor(entity); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java index 4a896b63..f75545c5 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java @@ -1,5 +1,6 @@ package org.mvplugins.multiverse.portals.listeners; +import com.dumptruckman.minecraft.util.Logging; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; @@ -8,23 +9,29 @@ import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.core.teleportation.BlockSafety; import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; +import org.mvplugins.multiverse.portals.config.PortalsConfig; +import org.mvplugins.multiverse.portals.event.MVPortalEvent; import org.mvplugins.multiverse.portals.utils.PortalManager; @Service public final class MVPEntityPortalListener implements Listener { private final PortalManager portalManager; - private final AsyncSafetyTeleporter teleporter; + private final PortalsConfig portalsConfig; + private final BlockSafety blockSafety; @Inject MVPEntityPortalListener(@NotNull PortalManager portalManager, - @NotNull AsyncSafetyTeleporter teleporter) { + @NotNull PortalsConfig portalsConfig, + @NotNull BlockSafety blockSafety) { this.portalManager = portalManager; - this.teleporter = teleporter; + this.portalsConfig = portalsConfig; + this.blockSafety = blockSafety; } @EventHandler(ignoreCancelled = true) @@ -33,20 +40,27 @@ void entityPortal(EntityPortalEvent event) { Location location = entity.getLocation(); MVPortal portal = portalManager.getPortal(location); - if (portal == null || !portal.getTeleportNonPlayers()) { - return; + if (portal == null) { + // Check around the player to make sure + Location translatedLoc = this.blockSafety.findPortalBlockNextTo(event.getFrom()); + if (translatedLoc != null) { + Logging.finer("Entity was outside of portal, The location has been successfully translated."); + portal = portalManager.getPortal(translatedLoc); + } } - DestinationInstance destination = portal.getDestination(); - if (destination == null) { + if (portal == null) { + return; + } + if (!portal.getTeleportNonPlayers()) { + if (!portalsConfig.getPortalsDefaultToNether()) { + event.setCancelled(true); + } return; } - event.setCancelled(true); - - teleporter.to(destination) - .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) - .passengerMode(PassengerModes.RETAIN_ALL) - .teleportSingle(entity); + Logging.fine("[EntityPortalEvent] Portal action for entity: " + entity); + portal.runActionFor(entity) + .onSuccess(() -> event.setCancelled(true)); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java index 256b9b83..5cb944e0 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java @@ -37,21 +37,15 @@ public final class MVPPlayerMoveListener implements Listener { private final MultiversePortals plugin; private final PortalsConfig portalsConfig; private final PlayerListenerHelper helper; - private final WorldManager worldManager; - private final AsyncSafetyTeleporter teleporter; @Inject MVPPlayerMoveListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, - @NotNull PlayerListenerHelper helper, - @NotNull WorldManager worldManager, - @NotNull AsyncSafetyTeleporter teleporter) { + @NotNull PlayerListenerHelper helper) { this.plugin = plugin; this.portalsConfig = portalsConfig; this.helper = helper; - this.worldManager = worldManager; - this.teleporter = teleporter; } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) @@ -77,23 +71,8 @@ void playerMove(PlayerMoveEvent event) { return; } - DestinationInstance destination = portal.getDestination(); - if (destination == null) { - Logging.fine("Invalid Destination!"); - return; - } player.setFallDistance(0); - Location destLocation = destination.getLocation(player).getOrNull(); - if (destLocation == null) { - Logging.fine("Unable to teleport player because destination is null!"); - return; - } - - if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { - Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); - return; - } if (!portal.isFrameValid(loc)) { player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material. You should exit it now."); return; @@ -115,7 +94,7 @@ void playerMove(PlayerMoveEvent event) { } // call event for other plugins - MVPortalEvent portalEvent = new MVPortalEvent(destination, event.getPlayer(), portal); + MVPortalEvent portalEvent = new MVPortalEvent(portal.getDestination(), event.getPlayer(), portal); this.plugin.getServer().getPluginManager().callEvent(portalEvent); if (portalEvent.isCancelled()) { return; @@ -124,18 +103,7 @@ void playerMove(PlayerMoveEvent event) { helper.payPortalEntryFee(portal, player); } - teleporter.to(destination) - .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) - .passengerMode(portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE) - .teleportSingle(player) - .onSuccess(() -> { - ps.playerDidTeleport(destLocation); - ps.setTeleportTime(new Date()); - helper.stateSuccess(player.getDisplayName(), destination.toString()); - }) - .onFailure(reason -> Logging.fine( - "Failed to teleport player '%s' to destination '%s'. Reason: %s", - player.getDisplayName(), destination, reason) - ); + Logging.fine("[PlayerMoveEvent] Portal action for player: " + player); + portal.runActionFor(player); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java index adf83bff..44bd3e99 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java @@ -30,7 +30,6 @@ final class MVPPlayerPortalListener implements PortalsListener { private final WorldManager worldManager; private final MultiversePortals plugin; private final PlayerListenerHelper helper; - private final AsyncSafetyTeleporter teleporter; @Inject MVPPlayerPortalListener(@NotNull PortalManager portalManager, @@ -38,15 +37,13 @@ final class MVPPlayerPortalListener implements PortalsListener { @NotNull BlockSafety blockSafety, @NotNull WorldManager worldManager, @NotNull MultiversePortals plugin, - @NotNull PlayerListenerHelper helper, - @NotNull AsyncSafetyTeleporter teleporter) { + @NotNull PlayerListenerHelper helper) { this.portalManager = portalManager; this.portalsConfig = portalsConfig; this.blockSafety = blockSafety; this.worldManager = worldManager; this.plugin = plugin; this.helper = helper; - this.teleporter = teleporter; } @EventHandler(ignoreCancelled = true) @@ -72,47 +69,15 @@ void playerPortal(PlayerPortalEvent event) { } Logging.finer("There was a portal found!"); - DestinationInstance portalDest = portal.getDestination(); - if (portalDest == null) { - if (!portalsConfig.getPortalsDefaultToNether()) { - // If portals should not default to the nether, cancel the event - player.sendMessage(String.format( - "This portal %sdoesn't go anywhere. You should exit it now.", ChatColor.RED)); - Logging.fine("Event canceled because this was a MVPortal with an invalid destination. But you had 'portalsdefaulttonether' set to false!"); - event.setCancelled(true); - } - return; + if (!portalsConfig.getPortalsDefaultToNether()) { + event.setCancelled(true); } - // this is a valid MV Portal, so we'll cancel the event - event.setCancelled(true); - if (!portal.isFrameValid(playerPortalLoc)) { player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now."); return; } - Location destLocation = portalDest.getLocation(player).getOrNull(); - if (destLocation == null) { - Logging.fine("Unable to teleport player because destination is null!"); - return; - } - - if (!this.worldManager.isLoadedWorld(destLocation.getWorld())) { - Logging.fine("Unable to teleport player because the destination world is not managed by Multiverse!"); - return; - } - - if (portal.getCheckDestinationSafety() && portalDest.checkTeleportSafety()) { - Location safeLocation = blockSafety.findSafeSpawnLocation(portalDest.getLocation(player).getOrNull()); - if (safeLocation == null) { - Logging.warning("Portal " + portal.getName() + " destination is not safe!"); - player.sendMessage(ChatColor.RED + "Portal " + portal.getName() + " destination is not safe!"); - return; - } - destLocation = safeLocation; - } - PortalPlayerSession ps = this.plugin.getPortalSession(player); if (ps.checkAndSendCooldownMessage()) { Logging.fine("Player denied teleportation due to cooldown."); @@ -123,7 +88,7 @@ void playerPortal(PlayerPortalEvent event) { return; } - MVPortalEvent portalEvent = new MVPortalEvent(portalDest, player, portal); + MVPortalEvent portalEvent = new MVPortalEvent(portal.getDestination(), player, portal); this.plugin.getServer().getPluginManager().callEvent(portalEvent); if (portalEvent.isCancelled()) { @@ -134,9 +99,8 @@ void playerPortal(PlayerPortalEvent event) { helper.payPortalEntryFee(portal, player); } - teleporter.to(destLocation) - .passengerMode(portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE) - .teleportSingle(player) - .onFailure(() -> Logging.warning("Could not teleport to destination!")); + Logging.fine("[PlayerPortalEvent] Portal action for player: " + player); + portal.runActionFor(player) + .onSuccess(() -> event.setCancelled(true)); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java index e353daff..59ffcb39 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -8,6 +8,7 @@ package org.mvplugins.multiverse.portals.listeners; import java.util.ArrayList; +import java.util.Date; import java.util.List; import com.dumptruckman.minecraft.util.Logging; @@ -101,14 +102,9 @@ void vehicleMove(VehicleMoveEvent event) { } } - DestinationInstance destination = portal.getDestination(); - safetyTeleporter.to(destination) - .checkSafety(portal.getCheckDestinationSafety() && destination.checkTeleportSafety()) - .passengerMode(PassengerModes.RETAIN_ALL) - .teleportSingle(vehicle) - .onSuccess(() -> Logging.finer("Successfully teleported vehicle %s using portal %s", - vehicle.getName(), portal.getName())) - .onFailure(failures -> Logging.finer("Failed to teleport vehicle %s using portal %s. Failures: %s", - vehicle.getName(), portal.getName(), failures)); + Logging.fine("[VehicleMoveEvent] Portal action for vehicle: " + vehicle); + portal.runActionFor(vehicle) + .onSuccess(() -> playerPassengers.forEach(player -> + plugin.getPortalSession(player).setTeleportTime(new Date()))); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java b/src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java new file mode 100644 index 00000000..cf0217eb --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.portals.utils; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.portals.MultiversePortals; + +import java.util.Collections; +import java.util.List; + +@ApiStatus.Internal +@Service +public final class BungeeServerList implements Listener, PluginMessageListener { + + @NotNull + private final MultiversePortals plugin; + private boolean didFirstRun = false; + private List serverNames; + + @Inject + BungeeServerList(@NotNull MultiversePortals plugin) { + this.plugin = plugin; + serverNames = Collections.emptyList(); + Bukkit.getPluginManager().registerEvents(this, plugin); + Bukkit.getMessenger().registerIncomingPluginChannel(plugin, "BungeeCord", this); + } + + public List getServerNames() { + return serverNames; + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { + if (!channel.equals("BungeeCord")) { + return; + } + ByteArrayDataInput in = ByteStreams.newDataInput(message); + String subchannel = in.readUTF(); + if (subchannel.equals("GetServers")) { + // This is our response to the PlayerCount request + serverNames = List.of(in.readUTF().split(", ")); + Logging.fine("BungeeCord GetServers: " + String.join(", ", serverNames)); + } + didFirstRun = true; + } + + @EventHandler + private void playerJoin(PlayerJoinEvent event) { + if (didFirstRun) { + return; + } + Bukkit.getScheduler().runTaskLater(plugin, () -> { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("GetServers"); + Bukkit.getServer().sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + Logging.fine("Calling BungeeCord GetServers"); + }, 10); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java b/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java index a50cd199..d3184f2c 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java +++ b/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java @@ -27,43 +27,59 @@ public class DisplayUtils { public void showStaticInfo(CommandSender sender, MVPortal portal, String message) { sender.sendMessage(ChatColor.AQUA + "--- " + message + ChatColor.DARK_AQUA + portal.getName() + ChatColor.AQUA + " ---"); String[] locParts = portal.getPortalLocation().toString().split(":"); - sender.sendMessage("Coords: " + ChatColor.GOLD + locParts[0] + ChatColor.WHITE + " to " + ChatColor.GOLD + locParts[1] + ChatColor.WHITE + " in " + ChatColor.GOLD + portal.getWorld().getName() ); - if (portal.getDestination() == null) { - sender.sendMessage("Destination: " + ChatColor.RED + ChatColor.ITALIC + "NOT SET!"); + sender.sendMessage("Coords: " + ChatColor.GOLD + locParts[1] + ChatColor.WHITE + " to " + ChatColor.GOLD + locParts[2] + ChatColor.WHITE + " in " + ChatColor.GOLD + portal.getWorld().getName() ); + sender.sendMessage("Action Type: " + ChatColor.GOLD + portal.getActionType()); + if (portal.getAction().isEmpty()) { + sender.sendMessage("Action: " + ChatColor.RED + ChatColor.ITALIC + "NOT SET!"); + return; + } + if (portal.getActionType().equalsIgnoreCase("multiverse-destination")) { + sender.sendMessage("Action: " + ChatColor.GOLD + formatActionAsMVDestination(portal)); } else { - String destination = portal.getDestination().toString(); - String destType = portal.getDestination().getIdentifier(); - if (destType.equals("w")) { - MultiverseWorld destWorld = worldManager.getWorld(destination).getOrNull(); - if (destWorld != null) { - destination = "(World) " + ChatColor.DARK_AQUA + destination; - } - } - if (destType.equals("p")) { - // todo: I think should use instance check instead of destType prefix - // String targetWorldName = portalManager.getPortal(portal.getDestination().getName()).getWorld().getName(); - // destination = "(Portal) " + ChatColor.DARK_AQUA + portal.getDestination().getName() + ChatColor.GRAY + " (" + targetWorldName + ")"; - } - if (destType.equals("e")) { - String destinationWorld = portal.getDestination().toString().split(":")[1]; - String destPart = portal.getDestination().toString().split(":")[2]; - String[] targetParts = destPart.split(","); - int x, y, z; - try { - x = (int) Double.parseDouble(targetParts[0]); - y = (int) Double.parseDouble(targetParts[1]); - z = (int) Double.parseDouble(targetParts[2]); - } catch (NumberFormatException e) { - e.printStackTrace(); - return; - } - destination = "(Location) " + ChatColor.DARK_AQUA + destinationWorld + ", " + x + ", " + y + ", " + z; + sender.sendMessage("Action: " + ChatColor.GOLD + portal.getAction()); + } + sender.sendMessage("Check Destination Safety: " + formatBoolean(portal.getCheckDestinationSafety())); + sender.sendMessage("Teleport Non Players: " + formatBoolean(portal.getTeleportNonPlayers())); + } + + private String formatBoolean(Boolean bool) { + return bool ? ChatColor.GREEN + "true" : ChatColor.RED + "false"; + } + + public String formatActionAsMVDestination(MVPortal portal) { + String[] split = portal.getAction().split(":", 2); + String destination = split.length == 2 ? split[1] : ""; + String destType = split.length == 2 ? split[0] : ""; + if (destType.equals("w")) { + MultiverseWorld destWorld = worldManager.getWorld(destination).getOrNull(); + if (destWorld != null) { + return "(World) " + ChatColor.DARK_AQUA + destination; } - if (destType.equals("i")) { - destination = ChatColor.RED + "Invalid Destination!"; + } + if (destType.equals("p")) { + // todo: I think should use instance check instead of destType prefix + // String targetWorldName = portalManager.getPortal(portal.getDestination().getName()).getWorld().getName(); + // destination = "(Portal) " + ChatColor.DARK_AQUA + portal.getDestination().getName() + ChatColor.GRAY + " (" + targetWorldName + ")"; + } + if (destType.equals("e")) { + String destinationWorld = portal.getAction().split(":")[1]; + String destPart = portal.getAction().split(":")[2]; + String[] targetParts = destPart.split(","); + int x, y, z; + try { + x = (int) Double.parseDouble(targetParts[0]); + y = (int) Double.parseDouble(targetParts[1]); + z = (int) Double.parseDouble(targetParts[2]); + } catch (NumberFormatException e) { + e.printStackTrace(); + return ChatColor.RED + "Invalid Exact World Location!"; } - sender.sendMessage("Destination: " + ChatColor.GOLD + destination); + return "(Location) " + ChatColor.DARK_AQUA + destinationWorld + ", " + x + ", " + y + ", " + z; + } + if (destType.equals("i")) { + return ChatColor.RED + "Invalid Destination!"; } + return ChatColor.DARK_AQUA + portal.getAction(); } public void showPortalPriceInfo(MVPortal portal, CommandSender sender) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 75dfa420..9f44d426 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,4 +4,4 @@ authors: ['Rigby', 'fernferret', 'benwoo1110'] version: ${version} api-version: 1.13 depend: ['Multiverse-Core'] -softdepend: ['WorldEdit'] +softdepend: ['WorldEdit', 'PlaceholderAPI'] From bd793e90812c072c9194d93bf33b321959f480d6 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:09:05 +0800 Subject: [PATCH 06/10] Enhance action handler framework with detailed documentation and improved parsing methods --- .../multiverse/portals/MVPortal.java | 39 +++++++-- .../multiverse/portals/MVPortalNodes.java | 12 +-- .../portals/PortalPlayerSession.java | 5 +- .../portals/action/ActionFailureReason.java | 7 ++ .../portals/action/ActionHandler.java | 70 +++++++++++++++- .../portals/action/ActionHandlerProvider.java | 83 +++++++++++++++++-- .../portals/action/ActionHandlerType.java | 56 +++++++++++-- .../types}/BungeeServerList.java | 10 +-- .../action/types/CommandActionHandler.java | 16 +++- .../types/CommandActionHandlerType.java | 12 ++- .../portals/action/types/CommandRunner.java | 28 ++++--- .../MultiverseDestinationActionHandler.java | 16 +++- ...ultiverseDestinationActionHandlerType.java | 11 ++- .../action/types/ServerActionHandler.java | 14 +++- .../action/types/ServerActionHandlerType.java | 13 ++- .../portals/commands/ModifyCommand.java | 9 +- .../listeners/MVPEntityMoveListener.java | 9 +- .../listeners/MVPEntityPortalListener.java | 10 +-- .../listeners/MVPPlayerMoveListener.java | 19 +---- .../listeners/MVPPlayerPortalListener.java | 24 +++--- .../portals/listeners/MVPVehicleListener.java | 12 +-- ...rHelper.java => PortalListenerHelper.java} | 9 +- .../portals/utils/DisplayUtils.java | 53 +++++++++--- 23 files changed, 419 insertions(+), 118 deletions(-) rename src/main/java/org/mvplugins/multiverse/portals/{utils => action/types}/BungeeServerList.java (90%) rename src/main/java/org/mvplugins/multiverse/portals/listeners/{PlayerListenerHelper.java => PortalListenerHelper.java} (92%) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java index db27eada..65022709 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java @@ -18,6 +18,8 @@ import java.util.regex.Pattern; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; @@ -32,6 +34,7 @@ import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.teleportation.BlockSafety; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.vavr.control.Option; @@ -298,41 +301,53 @@ public PortalLocation getPortalLocation() { return this.location; } + @ApiStatus.AvailableSince("5.2") public Try setActionType(ActionHandlerType actionType) { return configHandle.set(configNodes.actionType, actionType.getName()); } + @ApiStatus.AvailableSince("5.2") public Try setActionType(String actionType) { return configHandle.set(configNodes.actionType, actionType); } + @ApiStatus.AvailableSince("5.2") public String getActionType() { return this.configHandle.get(configNodes.actionType); } + @ApiStatus.AvailableSince("5.2") public Try setAction(String action) { return configHandle.set(configNodes.action, action); } + @ApiStatus.AvailableSince("5.2") public String getAction() { return configHandle.get(configNodes.action); } + @ApiStatus.AvailableSince("5.2") public Attempt, ActionFailureReason> getActionHandler() { return actionHandlerProvider.parseHandler(getActionType(), getAction()); } + @ApiStatus.AvailableSince("5.2") + public Attempt, ActionFailureReason> getActionHandler(CommandSender sender) { + return actionHandlerProvider.parseHandler(sender, getActionType(), getAction()); + } + + @ApiStatus.AvailableSince("5.2") public Attempt runActionFor(Entity entity) { - return getActionHandler() + return getActionHandler(entity) .mapAttempt(actionHandler -> actionHandler.runAction(this, entity)) .onSuccess(() -> { if (entity instanceof Player player) { plugin.getPortalSession(player).setTeleportTime(new Date()); } }) - .onFailure(failure -> { - Logging.warning(failure.getFailureMessage().formatted(commandManager.getCommandIssuer(entity))); - }); + .onFailure(failure -> + Logging.warning(ChatTextFormatter.removeColor("Invalid Portal Action: " + + failure.getFailureMessage().formatted(commandManager.getCommandIssuer(Bukkit.getConsoleSender()))))); } /** @@ -643,7 +658,11 @@ public PortalLocation getLocation() { return getPortalLocation(); } - + /** + * @deprecated Portals now have new types of action. Hence, the portal's destination (now called action) may not + * always be a multiverse destination. It can be a command or server name as well. + * Please see {@link MVPortal#getActionHandler()} instead. + */ @Deprecated(since = "5.2", forRemoval = true) @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public boolean setDestination(String destinationString) { @@ -651,6 +670,11 @@ public boolean setDestination(String destinationString) { return setDestination(newDestination); } + /** + * @deprecated Portals now have new types of action. Hence, the portal's destination (now called action) may not + * always be a multiverse destination. It can be a command or server name as well. + * Please see {@link MVPortal#getActionHandler()} instead. + */ @Deprecated(since = "5.2", forRemoval = true) @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public boolean setDestination(DestinationInstance newDestination) { @@ -665,6 +689,11 @@ public boolean setDestination(DestinationInstance newDestination) { return this.configHandle.set(configNodes.action, newDestination.toString()).isSuccess(); } + /** + * @deprecated Portals now have new types of action. Hence, the portal's destination (now called action) may not + * always be a multiverse destination. It can be a command or server name as well. + * Please see {@link MVPortal#getActionHandler()} instead. + */ @Deprecated(since = "5.2", forRemoval = true) @ApiStatus.ScheduledForRemoval(inVersion = "6.0") public DestinationInstance getDestination() { diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java index f3098eef..34ef7e61 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java @@ -6,13 +6,11 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode; import org.mvplugins.multiverse.core.config.node.Node; import org.mvplugins.multiverse.core.config.node.NodeGroup; -import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer; import org.mvplugins.multiverse.core.destination.DestinationsProvider; import org.mvplugins.multiverse.core.exceptions.MultiverseException; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.portals.action.ActionHandler; import org.mvplugins.multiverse.portals.action.ActionHandlerProvider; -import org.mvplugins.multiverse.portals.action.ActionHandlerType; import org.mvplugins.multiverse.portals.utils.MultiverseRegion; import java.util.Collections; @@ -24,13 +22,11 @@ final class MVPortalNodes { private MultiversePortals plugin; private MVPortal portal; - private DestinationsProvider destinationsProvider; private ActionHandlerProvider actionHandlerProvider; MVPortalNodes(MultiversePortals plugin, MVPortal portal) { this.plugin = plugin; this.portal = portal; - this.destinationsProvider = MultiverseCoreApi.get().getDestinationsProvider(); this.actionHandlerProvider = plugin.getServiceLocator().getService(ActionHandlerProvider.class); } @@ -96,7 +92,7 @@ private N node(N node) { .build()); final ConfigNode actionType = node(ConfigNode.builder("action-type", String.class) - .suggester(input -> actionHandlerProvider.getAllHandlerTypes()) + .suggester(input -> actionHandlerProvider.getAllHandlerTypeNames()) .defaultValue("multiverse-destination") .build()); @@ -106,6 +102,12 @@ private N node(N node) { .suggester((sender, input) -> actionHandlerProvider.getHandlerType(portal.getActionType()) .map(actionHandlerType -> actionHandlerType.suggestActions(sender, input)) .getOrElse(Collections.emptyList())) + .stringParser((sender, input, type) -> + Try.of(() -> actionHandlerProvider.getHandlerType(portal.getActionType()) + .mapAttempt(actionHandlerType -> actionHandlerType.parseHandler(sender, input)) + .map(ActionHandler::serialise) + .getOrThrow(failure -> + new MultiverseException(failure.getFailureMessage())))) .build()); final ConfigNode checkDestinationSafety = node(ConfigNode.builder("check-destination-safety", Boolean.class) diff --git a/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java b/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java index 658a4c23..c5c2f4f5 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java +++ b/src/main/java/org/mvplugins/multiverse/portals/PortalPlayerSession.java @@ -252,17 +252,14 @@ public boolean showDebugInfo() { } displayUtils.showStaticInfo(this.player, this.standingIn, "You are currently standing in "); - displayUtils.showPortalPriceInfo(this.standingIn, this.player); return true; } public boolean showDebugInfo(MVPortal portal) { if (portal.playerCanEnterPortal(this.player)) { displayUtils.showStaticInfo(this.player, portal, "Portal Info "); - displayUtils.showPortalPriceInfo(portal, this.player); - } else { - Logging.info("Player " + this.player.getName() + " walked through" + portal.getName() + " with debug on."); } + Logging.info("Player " + this.player.getName() + " walked through" + portal.getName() + " with debug on."); return true; } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java index 1aaa7332..8982d595 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java @@ -1,7 +1,14 @@ package org.mvplugins.multiverse.portals.action; import org.mvplugins.multiverse.core.utils.result.FailureReason; +import org.mvplugins.multiverse.external.jetbrains.annotations.ApiStatus; +/** + * Parent class for all reasons for a failure when trying to parse an action handler. + * + * @since 5.2 + */ +@ApiStatus.AvailableSince("5.2") public interface ActionFailureReason extends FailureReason { ActionFailureReason INSTANCE = new ActionFailureReason() {}; } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java index 792273f7..a3a0d4da 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java @@ -1,20 +1,84 @@ package org.mvplugins.multiverse.portals.action; import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; +/** + * Represents an action handler that performs a specific action when an entity uses a portal. + * + * @param The type of the action handler + * @param The specific action handler class + * + * @since 5.2 + */ +@ApiStatus.AvailableSince("5.2") public abstract class ActionHandler, H extends ActionHandler> { private final T handlerType; - protected ActionHandler(T handlerType) { + /** + * Create a new action handler. + * + * @param handlerType The type of this action handler + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + protected ActionHandler(@NotNull T handlerType) { this.handlerType = handlerType; } - public T getHandlerType() { + /** + * Get the type of this action handler. + * + * @return The action handler type + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull T getHandlerType() { return handlerType; } - public abstract Attempt runAction(MVPortal portal, Entity entity); + /** + * Run the action for the given portal and entity. + * + * @param portal The portal being used + * @param entity The entity using the portal + * @return An attempt indicating success or failure of the action + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public abstract @NotNull Attempt runAction(@NotNull MVPortal portal, @NotNull Entity entity); + + /** + * Get a description of the action for display purposes. + * + * @param entity The entity to describe the action for + * @return The action description message + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public abstract @NotNull Message actionDescription(Entity entity); + + /** + * Serialise the action handler back into a string. + * + * @return The serialised action handler + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull abstract String serialise(); + + @Override + public String toString() { + return serialise(); + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java index 3560331a..ca5ff090 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java @@ -1,35 +1,106 @@ package org.mvplugins.multiverse.portals.action; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.vavr.control.Option; import java.util.Collection; import java.util.HashMap; import java.util.Map; +/** + * Provides various actions that can be performed when a portal is used. This extends to more than just teleporting. + *
+ * Built-in action handler types include "server", "command", and "multiverse-destination", which can be used to + * perform actions such as sending players to different servers, executing commands, or teleporting to predefined + * destinations, respectively. + *
+ * You can also create custom action handler types by implementing the {@link ActionHandlerType} class and + * registering them with this provider. + * + * @since 5.2 + */ +@ApiStatus.AvailableSince("5.2") @Service public final class ActionHandlerProvider { private final Map> handlerTypeMap = new HashMap<>(); - public void registerHandlerType(ActionHandlerType handlerType) { + /** + * Register a new action handler type. + * + * @param handlerType The action handler type to register + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public void registerHandlerType(@NotNull ActionHandlerType handlerType) { handlerTypeMap.put(handlerType.getName(), handlerType); } - public Collection getAllHandlerTypes() { + /** + * Get all registered handler types names. + * + * @return Names of all registered handler types + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull Collection getAllHandlerTypeNames() { return handlerTypeMap.keySet(); } - public Attempt, ActionFailureReason> getHandlerType(String name) { + /** + * Get the handler type by name. + * + * @param name The name of the handler type + * @return The handler type, or a failure reason if not found + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull Attempt, ActionFailureReason> getHandlerType(@NotNull String name) { return Option.of(handlerTypeMap.get(name)) .map(Attempt::, ActionFailureReason>success) - .getOrElse(() -> Attempt.failure(ActionFailureReason.INSTANCE, Message.of("Unknown action type: " + name))); + .getOrElse(() -> Attempt.failure(ActionFailureReason.INSTANCE, + Message.of("Unknown action type '" + name + "'. Supported types are: " + + String.join(", ", handlerTypeMap.keySet())))); } - public Attempt, ActionFailureReason> parseHandler(String actionType, String action) { + /** + * Parse the action handler from the given type and action string, using the console as the command sender. + * + * @param actionType The type of the action handler + * @param action The action string + * @return The parsed action handler, or a failure reason if type is invalid or action string is of invalid format. + * + * @since 5.2 + */ + public @NotNull Attempt, ActionFailureReason> parseHandler(@NotNull String actionType, + @NotNull String action) { + return parseHandler(Bukkit.getConsoleSender(), actionType, action); + } + + /** + * Parse the action handler from the given type and action string. + * + * @param sender The command sender parsing the action + * @param actionType The type of the action handler + * @param action The action string + * @return The parsed action handler, or a failure reason if type is invalid or action string is of invalid format. + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull Attempt, ActionFailureReason> parseHandler(@NotNull CommandSender sender, + @NotNull String actionType, + @NotNull String action) { return getHandlerType(actionType) - .mapAttempt(actionHandlerType -> actionHandlerType.parseHandler(action)); + .mapAttempt(actionHandlerType -> actionHandlerType.parseHandler(sender, action)); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java index 7a533296..3a3c3130 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java @@ -3,28 +3,74 @@ import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Contract; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; -import org.mvplugins.multiverse.external.vavr.control.Option; import java.util.Collection; import java.util.Collections; +/** + * The action handler. + * + * @param This action handler type + * @param The action handler for this type + * + * @since 5.2 + */ +@ApiStatus.AvailableSince("5.2") @Contract public abstract class ActionHandlerType, H extends ActionHandler> { private final String name; - protected ActionHandlerType(String name) { + /** + * Create a new action handler type. The name must be unique among all action handler types. + *
+ * There is already 3 built-in action handler types: "server", "command", and "multiverse-destination". + * + * @param name The name of the action handler type + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + protected ActionHandlerType(@NotNull String name) { this.name = name; } - public String getName() { + /** + * Get the name of the action handler type. + * + * @return The name of the action handler type + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull String getName() { return name; } - public abstract Attempt parseHandler(String action); + /** + * Parse the action string into an action handler. + * + * @param action The action string + * @return The parsed action handler or a failure reason + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public abstract @NotNull Attempt parseHandler(@NotNull CommandSender sender, @NotNull String action); - public @NotNull Collection suggestActions(CommandSender sender, String input) { + /** + * Suggest action arguments for the given input use for command tab completion. + * + * @param sender The sender running the command + * @param input The current input + * @return A collection of suggestions + * + * @since 5.2 + */ + @ApiStatus.AvailableSince("5.2") + public @NotNull Collection suggestActions(@NotNull CommandSender sender, @NotNull String input) { return Collections.emptyList(); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java similarity index 90% rename from src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java rename to src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java index cf0217eb..a7e7cf5e 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/utils/BungeeServerList.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java @@ -1,4 +1,4 @@ -package org.mvplugins.multiverse.portals.utils; +package org.mvplugins.multiverse.portals.action.types; import com.dumptruckman.minecraft.util.Logging; import com.google.common.io.ByteArrayDataInput; @@ -16,12 +16,12 @@ import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.portals.MultiversePortals; +import java.util.Collection; import java.util.Collections; import java.util.List; -@ApiStatus.Internal @Service -public final class BungeeServerList implements Listener, PluginMessageListener { +final class BungeeServerList implements Listener, PluginMessageListener { @NotNull private final MultiversePortals plugin; @@ -36,7 +36,7 @@ public final class BungeeServerList implements Listener, PluginMessageListener { Bukkit.getMessenger().registerIncomingPluginChannel(plugin, "BungeeCord", this); } - public List getServerNames() { + Collection getServerNames() { return serverNames; } @@ -56,7 +56,7 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla } @EventHandler - private void playerJoin(PlayerJoinEvent event) { + void playerJoin(PlayerJoinEvent event) { if (didFirstRun) { return; } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java index 1364aafc..fae95706 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java @@ -1,7 +1,10 @@ package org.mvplugins.multiverse.portals.action.types; +import org.bukkit.ChatColor; import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.action.ActionFailureReason; import org.mvplugins.multiverse.portals.action.ActionHandler; @@ -16,8 +19,19 @@ final class CommandActionHandler extends ActionHandler runAction(MVPortal portal, Entity entity) { + public @NotNull Attempt runAction(@NotNull MVPortal portal, @NotNull Entity entity) { commandRunner.runCommand(entity); return Attempt.success(null); } + + @Override + public @NotNull Message actionDescription(Entity entity) { + return Message.of(ChatColor.AQUA + "Runs command " + commandRunner.cmdType + " " + ChatColor.GOLD + "/" + + commandRunner.parseCmdStrPlaceholders(entity)); + } + + @Override + public @NotNull String serialise() { + return commandRunner.rawCmd; + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java index 1a7f3d94..c8b9f6b0 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java @@ -2,12 +2,15 @@ import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.action.ActionFailureReason; import org.mvplugins.multiverse.portals.action.ActionHandlerType; import java.util.Collection; +import java.util.List; @Service final class CommandActionHandlerType extends ActionHandlerType { @@ -18,13 +21,18 @@ final class CommandActionHandlerType extends ActionHandlerType parseHandler(String action) { + public @NotNull Attempt parseHandler(@NotNull CommandSender sender, + @NotNull String action) { + if (action.isEmpty()) { + return Attempt.failure(ActionFailureReason.INSTANCE, + Message.of("Please specific a valid command to run as the portal's action.")); + } return Attempt.success(new CommandActionHandler(this, CommandRunner.fromString(action))); } @Override public Collection suggestActions(CommandSender sender, String input) { //todo possibly use command map to suggest tab completion - return super.suggestActions(sender, input); + return List.of("op:", "console:"); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java index 7d55e26f..6161688f 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java @@ -12,23 +12,27 @@ abstract class CommandRunner { static CommandRunner fromString(String command) { String[] split = REPatterns.COLON.split(command, 2); return switch ((split.length == 2) ? split[0] : "") { - case "op" -> new Op(split[1]); - case "console" -> new Console(split[1]); - default -> new Self(command); + case "op" -> new Op(command, split[1]); + case "console" -> new Console(command, split[1]); + default -> new Self(command, command); }; } + final String rawCmd; private final String cmdStr; + final String cmdType; - CommandRunner(String cmdStr) { + private CommandRunner(String rawCmd, String cmdStr, String cmdType) { + this.rawCmd = rawCmd; this.cmdStr = cmdStr; + this.cmdType = cmdType; } void runCommand(CommandSender sender) { - runCommand(sender, parseCmdStr(sender)); + runCommand(sender, parseCmdStrPlaceholders(sender)); } - private String parseCmdStr(CommandSender sender) { + String parseCmdStrPlaceholders(CommandSender sender) { String parsedCmd = cmdStr; if (sender instanceof Entity entity) { parsedCmd = parsedCmd.replace("%world%", entity.getWorld().getName()); @@ -46,8 +50,8 @@ private String parseCmdStr(CommandSender sender) { private static class Self extends CommandRunner { - private Self(String cmdStr) { - super(cmdStr); + private Self(String rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "myself"); } @Override @@ -58,8 +62,8 @@ protected void runCommand(CommandSender sender, String cmd) { private static class Op extends CommandRunner { - private Op(String cmdStr) { - super(cmdStr); + private Op(String rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "as operator"); } @Override @@ -78,8 +82,8 @@ protected void runCommand(CommandSender sender, String cmd) { private static class Console extends CommandRunner { - private Console(String cmdStr) { - super(cmdStr); + private Console(String rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "from console"); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java index 565d2bb3..5aab7da1 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java @@ -1,13 +1,16 @@ package org.mvplugins.multiverse.portals.action.types; import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.ChatColor; import org.bukkit.entity.Entity; import org.bukkit.entity.Vehicle; import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.PassengerMode; import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.action.ActionFailureReason; import org.mvplugins.multiverse.portals.action.ActionHandler; @@ -26,7 +29,7 @@ final class MultiverseDestinationActionHandler extends ActionHandler runAction(MVPortal portal, Entity entity) { + public @NotNull Attempt runAction(@NotNull MVPortal portal, @NotNull Entity entity) { teleporter.to(destinationInstance) .checkSafety(portal.getCheckDestinationSafety() && destinationInstance.checkTeleportSafety()) .passengerMode(passengerModeFor(portal, entity)) @@ -38,10 +41,21 @@ public Attempt runAction(MVPortal portal, Entity enti return Attempt.success(null); } + @Override + public @NotNull Message actionDescription(Entity entity) { + //todo use v5.4's DestinationInstance#getDisplayMessage method + return Message.of(ChatColor.AQUA + "Teleports to " + ChatColor.GOLD + destinationInstance.toString()); + } + private PassengerMode passengerModeFor(MVPortal portal, Entity entity) { if (entity instanceof Vehicle) { return PassengerModes.RETAIN_ALL; } return portal.getTeleportNonPlayers() ? PassengerModes.RETAIN_ALL : PassengerModes.DISMOUNT_VEHICLE; } + + @Override + public @NotNull String serialise() { + return destinationInstance.toString(); + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java index d8e68d8a..81b34820 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java @@ -3,6 +3,7 @@ import org.bukkit.command.CommandSender; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.external.jakarta.inject.Inject; @@ -27,15 +28,19 @@ final class MultiverseDestinationActionHandlerType extends ActionHandlerType parseHandler(String action) { - return destinationsProvider.parseDestination(action) + public @NotNull Attempt parseHandler(@NotNull CommandSender sender, + @NotNull String action) { + if (action.isEmpty()) { + return Attempt.failure(ActionFailureReason.INSTANCE, Message.of("Please specific a multiverse destination as the portal's action.")); + } + return destinationsProvider.parseDestination(sender, action) .transform(ActionFailureReason.INSTANCE) .map(destinationInstance -> new MultiverseDestinationActionHandler(this, teleporter, destinationInstance)); } @Override - public Collection suggestActions(CommandSender sender, String input) { + public @NotNull Collection suggestActions(@NotNull CommandSender sender, @NotNull String input) { return destinationsProvider.suggestDestinationStrings(sender, input); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java index d6b86904..b19d8692 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java @@ -2,10 +2,12 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import org.bukkit.ChatColor; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.external.vavr.control.Try; import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.MultiversePortals; @@ -24,7 +26,7 @@ final class ServerActionHandler extends ActionHandler runAction(MVPortal portal, Entity entity) { + public @NotNull Attempt runAction(@NotNull MVPortal portal, @NotNull Entity entity) { if (!(entity instanceof Player player)) { return Attempt.failure(ActionFailureReason.INSTANCE, Message.of("Only players can teleport between servers!")); } @@ -41,4 +43,14 @@ public Attempt runAction(MVPortal portal, Entity enti .getOrElse(() -> Attempt.failure(ActionFailureReason.INSTANCE, Message.of("An unknown error occurred while sending plugin message to proxy."))); } + + @Override + public Message actionDescription(Entity entity) { + return Message.of(ChatColor.AQUA + "Transfer to " + ChatColor.GOLD + serverName + ChatColor.AQUA + " server"); + } + + @Override + public @NotNull String serialise() { + return serverName; + } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java index e8769b68..9748a9f2 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java @@ -1,14 +1,14 @@ package org.mvplugins.multiverse.portals.action.types; import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.locale.message.Message; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MultiversePortals; import org.mvplugins.multiverse.portals.action.ActionFailureReason; import org.mvplugins.multiverse.portals.action.ActionHandlerType; -import org.mvplugins.multiverse.portals.utils.BungeeServerList; import java.util.Collection; @@ -26,12 +26,17 @@ final class ServerActionHandlerType extends ActionHandlerType parseHandler(String action) { + public @NotNull Attempt parseHandler(@NotNull CommandSender sender, + @NotNull String action) { + if (action.isEmpty()) { + return Attempt.failure(ActionFailureReason.INSTANCE, + Message.of("Please specific a server name as the portal's action.")); + } return Attempt.success(new ServerActionHandler(this, plugin, action)); } @Override - public Collection suggestActions(CommandSender sender, String input) { + public @NotNull Collection suggestActions(@NotNull CommandSender sender, @NotNull String input) { return bungeeServerList.getServerNames(); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java index cab34510..30c0633f 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java +++ b/src/main/java/org/mvplugins/multiverse/portals/commands/ModifyCommand.java @@ -68,10 +68,17 @@ public void onModifyCommand( var stringPropertyHandle = portal.getStringPropertyHandle(); stringPropertyHandle.setPropertyString(issuer.getIssuer(), property, value) .onSuccess(ignore -> { - this.plugin.savePortalsConfig(); + if (!this.plugin.savePortalsConfig()) { + issuer.sendError("Could not save portals configuration file!"); + return; + } issuer.sendMessage(ChatColor.GREEN + "Property " + ChatColor.AQUA + property + ChatColor.GREEN + " of Portal " + ChatColor.YELLOW + portal.getName() + ChatColor.GREEN + " was set to " + ChatColor.AQUA + stringPropertyHandle.getProperty(property).getOrNull()); + if (property.equalsIgnoreCase("action-type")) { + issuer.sendError("Please note that changing the action type &edoes NOT modify &cthe action itself. " + + "You need to modify the action property as well to ensure the portal works as expected."); + } }).onFailure(failure -> { issuer.sendError("Property " + ChatColor.AQUA + property + ChatColor.RED + " of Portal " + ChatColor.YELLOW + portal.getName() + ChatColor.RED + " was NOT set to " diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java index 2a931734..4c2ba8f8 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java @@ -7,9 +7,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; -import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; @@ -19,12 +16,12 @@ @Service public final class MVPEntityMoveListener implements Listener { - private final PlayerListenerHelper helper; + private final PortalListenerHelper helper; private final PortalManager portalManager; private final PortalsConfig portalsConfig; @Inject - MVPEntityMoveListener(@NotNull PlayerListenerHelper helper, + MVPEntityMoveListener(@NotNull PortalListenerHelper helper, @NotNull PortalManager portalManager, @NotNull PortalsConfig portalsConfig) { this.helper = helper; @@ -48,6 +45,8 @@ void entityMove(EntityMoveEvent event) { return; } + Logging.fine("[EntityMoveEvent] Portal action for entity: " + entity); + helper.stateSuccess(entity.getName(), portal.getName()); portal.runActionFor(entity); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java index f75545c5..2eb9ee8f 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java @@ -7,15 +7,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPortalEvent; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.BlockSafety; -import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.mvplugins.multiverse.portals.MVPortal; import org.mvplugins.multiverse.portals.config.PortalsConfig; -import org.mvplugins.multiverse.portals.event.MVPortalEvent; import org.mvplugins.multiverse.portals.utils.PortalManager; @Service @@ -24,14 +20,17 @@ public final class MVPEntityPortalListener implements Listener { private final PortalManager portalManager; private final PortalsConfig portalsConfig; private final BlockSafety blockSafety; + private final PortalListenerHelper helper; @Inject MVPEntityPortalListener(@NotNull PortalManager portalManager, @NotNull PortalsConfig portalsConfig, - @NotNull BlockSafety blockSafety) { + @NotNull BlockSafety blockSafety, + @NotNull PortalListenerHelper helper) { this.portalManager = portalManager; this.portalsConfig = portalsConfig; this.blockSafety = blockSafety; + this.helper = helper; } @EventHandler(ignoreCancelled = true) @@ -60,6 +59,7 @@ void entityPortal(EntityPortalEvent event) { } Logging.fine("[EntityPortalEvent] Portal action for entity: " + entity); + helper.stateSuccess(entity.getName(), portal.getName()); portal.runActionFor(entity) .onSuccess(() -> event.setCancelled(true)); } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java index 5cb944e0..0dcb2aab 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerMoveListener.java @@ -9,10 +9,6 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.event.Listener; -import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; -import org.mvplugins.multiverse.core.teleportation.PassengerModes; -import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; @@ -29,20 +25,18 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerMoveEvent; -import java.util.Date; - @Service public final class MVPPlayerMoveListener implements Listener { private final MultiversePortals plugin; private final PortalsConfig portalsConfig; - private final PlayerListenerHelper helper; + private final PortalListenerHelper helper; @Inject MVPPlayerMoveListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, - @NotNull PlayerListenerHelper helper) { + @NotNull PortalListenerHelper helper) { this.plugin = plugin; this.portalsConfig = portalsConfig; this.helper = helper; @@ -81,18 +75,11 @@ void playerMove(PlayerMoveEvent event) { return; } - PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + PortalListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); if (!portalUseResult.canUse()) { return; } - // If they're using Access and they don't have permission and they're NOT excempt, return, they're not allowed to tp. - // No longer checking exemption status - if (portalsConfig.getEnforcePortalAccess() && !event.getPlayer().hasPermission(portal.getPermission())) { - this.helper.stateFailure(player.getDisplayName(), portal.getName()); - return; - } - // call event for other plugins MVPortalEvent portalEvent = new MVPortalEvent(portal.getDestination(), event.getPlayer(), portal); this.plugin.getServer().getPluginManager().callEvent(portalEvent); diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java index 44bd3e99..b9a1fb5e 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPPlayerPortalListener.java @@ -7,10 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerPortalEvent; import org.jvnet.hk2.annotations.Service; -import org.mvplugins.multiverse.core.destination.DestinationInstance; -import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.BlockSafety; -import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; @@ -18,6 +15,7 @@ import org.mvplugins.multiverse.portals.MultiversePortals; import org.mvplugins.multiverse.portals.PortalPlayerSession; import org.mvplugins.multiverse.portals.config.PortalsConfig; +import org.mvplugins.multiverse.portals.enums.MoveType; import org.mvplugins.multiverse.portals.event.MVPortalEvent; import org.mvplugins.multiverse.portals.utils.PortalManager; @@ -27,21 +25,18 @@ final class MVPPlayerPortalListener implements PortalsListener { private final PortalManager portalManager; private final PortalsConfig portalsConfig; private final BlockSafety blockSafety; - private final WorldManager worldManager; private final MultiversePortals plugin; - private final PlayerListenerHelper helper; + private final PortalListenerHelper helper; @Inject MVPPlayerPortalListener(@NotNull PortalManager portalManager, @NotNull PortalsConfig portalsConfig, @NotNull BlockSafety blockSafety, - @NotNull WorldManager worldManager, @NotNull MultiversePortals plugin, - @NotNull PlayerListenerHelper helper) { + @NotNull PortalListenerHelper helper) { this.portalManager = portalManager; this.portalsConfig = portalsConfig; this.blockSafety = blockSafety; - this.worldManager = worldManager; this.plugin = plugin; this.helper = helper; } @@ -75,15 +70,21 @@ void playerPortal(PlayerPortalEvent event) { if (!portal.isFrameValid(playerPortalLoc)) { player.sendMessage("This portal's frame is made of an " + ChatColor.RED + "incorrect material." + ChatColor.RED + " You should exit it now."); + event.setCancelled(true); return; } PortalPlayerSession ps = this.plugin.getPortalSession(player); + ps.setStaleLocation(playerPortalLoc, MoveType.PLAYER_MOVE); + if (ps.showDebugInfo()) { + event.setCancelled(true); + return; + } if (ps.checkAndSendCooldownMessage()) { Logging.fine("Player denied teleportation due to cooldown."); return; } - PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + PortalListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); if (!portalUseResult.canUse()) { return; } @@ -100,7 +101,10 @@ void playerPortal(PlayerPortalEvent event) { } Logging.fine("[PlayerPortalEvent] Portal action for player: " + player); + helper.stateSuccess(player.getDisplayName(), portal.getName()); portal.runActionFor(player) - .onSuccess(() -> event.setCancelled(true)); + .onSuccess(() -> { + event.setCancelled(true); + }); } } diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java index 59ffcb39..561d9a31 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -13,9 +13,7 @@ import com.dumptruckman.minecraft.util.Logging; import org.bukkit.event.Listener; -import org.mvplugins.multiverse.core.destination.DestinationInstance; import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; -import org.mvplugins.multiverse.core.teleportation.PassengerModes; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; @@ -35,19 +33,16 @@ public final class MVPVehicleListener implements Listener { private final MultiversePortals plugin; private final PortalManager portalManager; - private final AsyncSafetyTeleporter safetyTeleporter; - private final PlayerListenerHelper helper; + private final PortalListenerHelper helper; @Inject MVPVehicleListener( @NotNull MultiversePortals plugin, @NotNull PortalManager portalManager, - @NotNull AsyncSafetyTeleporter safetyTeleporter, - @NotNull PlayerListenerHelper helper + @NotNull PortalListenerHelper helper ) { this.plugin = plugin; this.portalManager = portalManager; - this.safetyTeleporter = safetyTeleporter; this.helper = helper; } @@ -91,7 +86,7 @@ void vehicleMove(VehicleMoveEvent event) { } for (Player player : playerPassengers) { - PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + PortalListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); if (!portalUseResult.canUse()) { Logging.finer("Player %s is not allowed to use portal %s, removing them from the vehicle.", player.getName(), portal.getName()); @@ -103,6 +98,7 @@ void vehicleMove(VehicleMoveEvent event) { } Logging.fine("[VehicleMoveEvent] Portal action for vehicle: " + vehicle); + helper.stateSuccess(vehicle.getName(), portal.getName()); portal.runActionFor(vehicle) .onSuccess(() -> playerPassengers.forEach(player -> plugin.getPortalSession(player).setTeleportTime(new Date()))); diff --git a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java b/src/main/java/org/mvplugins/multiverse/portals/listeners/PortalListenerHelper.java similarity index 92% rename from src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java rename to src/main/java/org/mvplugins/multiverse/portals/listeners/PortalListenerHelper.java index fbecbb28..787ef938 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/PlayerListenerHelper.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/PortalListenerHelper.java @@ -12,13 +12,13 @@ import org.mvplugins.multiverse.portals.config.PortalsConfig; @Service -final class PlayerListenerHelper { +final class PortalListenerHelper { private final PortalsConfig portalsConfig; private final MVEconomist economist; @Inject - PlayerListenerHelper(@NotNull PortalsConfig portalsConfig, + PortalListenerHelper(@NotNull PortalsConfig portalsConfig, @NotNull MVEconomist economist) { this.portalsConfig = portalsConfig; this.economist = economist; @@ -31,10 +31,10 @@ boolean isWithinSameBlock(Location from, Location to) { && from.getBlockZ() == to.getBlockZ(); } - void stateSuccess(String playerName, String worldName) { + void stateSuccess(String playerName, String portalName) { Logging.fine(String.format( "MV-Portals is allowing Player '%s' to use the portal '%s'.", - playerName, worldName)); + playerName, portalName)); } void stateFailure(String playerName, String portalName) { @@ -62,6 +62,7 @@ PortalUseResult checkPlayerCanUsePortal(MVPortal portal, Player player) { if (price > 0D && !economist.isPlayerWealthyEnough(player, price, currency)) { player.sendMessage(economist.getNSFMessage(currency, "You need " + economist.formatPrice(price, currency) + " to enter the " + portal.getName() + " portal.")); + stateFailure(player.getDisplayName(), portal.getName()); return PortalUseResult.CANNOT_USE; } return PortalUseResult.PAID_USE; diff --git a/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java b/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java index d3184f2c..f04f7c29 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java +++ b/src/main/java/org/mvplugins/multiverse/portals/utils/DisplayUtils.java @@ -1,51 +1,80 @@ package org.mvplugins.multiverse.portals.utils; +import org.bukkit.entity.Entity; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.economy.MVEconomist; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.ApiStatus; import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; import org.mvplugins.multiverse.portals.MVPortal; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandler; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; @Service public class DisplayUtils { private final WorldManager worldManager; private final MVEconomist economist; + private final MVCommandManager commandManager; @Inject DisplayUtils( @NotNull WorldManager worldManager, - @NotNull MVEconomist economist) { + @NotNull MVEconomist economist, + @NotNull MVCommandManager commandManager) { this.worldManager = worldManager; this.economist = economist; + this.commandManager = commandManager; } public void showStaticInfo(CommandSender sender, MVPortal portal, String message) { - sender.sendMessage(ChatColor.AQUA + "--- " + message + ChatColor.DARK_AQUA + portal.getName() + ChatColor.AQUA + " ---"); + MVCommandIssuer issuer = commandManager.getCommandIssuer(sender); + + issuer.sendMessage(ChatColor.AQUA + "--- " + message + ChatColor.DARK_AQUA + portal.getName() + ChatColor.AQUA + " ---"); String[] locParts = portal.getPortalLocation().toString().split(":"); - sender.sendMessage("Coords: " + ChatColor.GOLD + locParts[1] + ChatColor.WHITE + " to " + ChatColor.GOLD + locParts[2] + ChatColor.WHITE + " in " + ChatColor.GOLD + portal.getWorld().getName() ); - sender.sendMessage("Action Type: " + ChatColor.GOLD + portal.getActionType()); + issuer.sendMessage(ChatColor.WHITE + "Coords: " + ChatColor.GOLD + locParts[1] + ChatColor.WHITE + " to " + ChatColor.GOLD + locParts[2] + ChatColor.WHITE + " in " + ChatColor.GOLD + portal.getWorld().getName() ); + issuer.sendMessage(ChatColor.WHITE + "Configured Action Type: " + ChatColor.GOLD + portal.getActionType()); if (portal.getAction().isEmpty()) { - sender.sendMessage("Action: " + ChatColor.RED + ChatColor.ITALIC + "NOT SET!"); - return; - } - if (portal.getActionType().equalsIgnoreCase("multiverse-destination")) { - sender.sendMessage("Action: " + ChatColor.GOLD + formatActionAsMVDestination(portal)); + issuer.sendMessage(ChatColor.WHITE + "Configured Action: " + ChatColor.RED + ChatColor.ITALIC + "NOT SET!"); } else { - sender.sendMessage("Action: " + ChatColor.GOLD + portal.getAction()); + issuer.sendMessage(ChatColor.WHITE + "Configured Action: " + ChatColor.GOLD + portal.getAction()); } - sender.sendMessage("Check Destination Safety: " + formatBoolean(portal.getCheckDestinationSafety())); - sender.sendMessage("Teleport Non Players: " + formatBoolean(portal.getTeleportNonPlayers())); + Attempt, ActionFailureReason> actionHandler = portal.getActionHandler(); + + issuer.sendMessage(ChatColor.WHITE + "Check Destination Safety: " + formatBoolean(portal.getCheckDestinationSafety())); + issuer.sendMessage(ChatColor.WHITE + "Teleport Non Players: " + formatBoolean(portal.getTeleportNonPlayers())); + showPortalPriceInfo(portal, sender); + + if (sender instanceof Entity entity) { + actionHandler.map(handler -> handler.actionDescription(entity)) + .onSuccess(actionMessage -> { + issuer.sendMessage(""); + issuer.sendMessage(Message.of(ChatColor.WHITE + "Your action when using Portal: {action}", + replace("{action}").with(actionMessage))); + }); + } + + actionHandler.onFailure(failure -> { + issuer.sendMessage(""); + issuer.sendError(failure.getFailureMessage()); + }); } private String formatBoolean(Boolean bool) { return bool ? ChatColor.GREEN + "true" : ChatColor.RED + "false"; } + @ApiStatus.AvailableSince("5.2") public String formatActionAsMVDestination(MVPortal portal) { String[] split = portal.getAction().split(":", 2); String destination = split.length == 2 ? split[1] : ""; From 53ac33409c2edfbf279019e2abf6dfc98acc6577 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:17:19 +0800 Subject: [PATCH 07/10] Refactor logging initialization and shutdown in MultiversePortals --- .../mvplugins/multiverse/portals/MultiversePortals.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java index 92b642a9..59ca04ec 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MultiversePortals.java @@ -84,13 +84,13 @@ public class MultiversePortals extends MultiverseModule { @Override public void onLoad() { super.onLoad(); + Logging.init(this); getDataFolder().mkdirs(); } @Override public void onEnable() { super.onEnable(); - Logging.init(this); initializeDependencyInjection(new MultiversePortalsPluginBinder(this)); @@ -122,7 +122,8 @@ public void onEnable() { // for teleporting between servers getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); - Logging.log(true, Level.INFO, " Enabled - By %s", StringFormatter.joinAnd(getDescription().getAuthors())); + Logging.config("Version %s (API v%s) Enabled - By %s", + this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); } @Override @@ -130,6 +131,8 @@ public void onDisable() { this.savePortalsConfig(); MultiversePortalsApi.shutdown(); shutdownDependencyInjection(); + Logging.info("- Disabled"); + Logging.shutdown(); } private boolean setupConfig() { From 6451700ef6c86093759f6b93bd0971a9d2985fe2 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 2 Jan 2026 23:44:50 +0800 Subject: [PATCH 08/10] Add support for re-querying BungeeCord server list on config reload --- .../action/types/BungeeServerList.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java index a7e7cf5e..5d414a79 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java @@ -10,9 +10,9 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.messaging.PluginMessageListener; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; import org.mvplugins.multiverse.external.jakarta.inject.Inject; import org.mvplugins.multiverse.portals.MultiversePortals; @@ -20,6 +20,9 @@ import java.util.Collections; import java.util.List; +/** + * Simple utility class to get the server list from BungeeCord compatible proxy if available. + */ @Service final class BungeeServerList implements Listener, PluginMessageListener { @@ -52,7 +55,6 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla serverNames = List.of(in.readUTF().split(", ")); Logging.fine("BungeeCord GetServers: " + String.join(", ", serverNames)); } - didFirstRun = true; } @EventHandler @@ -60,11 +62,21 @@ void playerJoin(PlayerJoinEvent event) { if (didFirstRun) { return; } - Bukkit.getScheduler().runTaskLater(plugin, () -> { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("GetServers"); - Bukkit.getServer().sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - Logging.fine("Calling BungeeCord GetServers"); - }, 10); + // Delay a bit to make sure player is fully logged in + Bukkit.getScheduler().runTaskLater(plugin, this::callBungeeCordGetServers, 10); + didFirstRun = true; + } + + @EventHandler + void configReload(MVConfigReloadEvent event) { + // On config reload, we should re-query BungeeCord for the server list + callBungeeCordGetServers(); + } + + private void callBungeeCordGetServers() { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("GetServers"); + Bukkit.getServer().sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + Logging.fine("Calling BungeeCord GetServers"); } } From fb9dfe8f8e9c62b28bd90c04a9ea0681d3215ff4 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 2 Jan 2026 23:47:43 +0800 Subject: [PATCH 09/10] Add setActionHandler method and update api annotations for improved clarity --- .../mvplugins/multiverse/portals/MVPortal.java | 17 +++++++++++++---- .../portals/action/ActionHandlerProvider.java | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java index 65022709..5ba6a310 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java @@ -326,6 +326,13 @@ public String getAction() { return configHandle.get(configNodes.action); } + @ApiStatus.AvailableSince("5.2") + public Try setActionHandler(ActionHandler action) { + Objects.requireNonNull(action, "action cannot be null"); + return setActionType(action.getHandlerType()) + .flatMap(v -> setAction(action.serialise())); + } + @ApiStatus.AvailableSince("5.2") public Attempt, ActionFailureReason> getActionHandler() { return actionHandlerProvider.parseHandler(getActionType(), getAction()); @@ -672,7 +679,7 @@ public boolean setDestination(String destinationString) { /** * @deprecated Portals now have new types of action. Hence, the portal's destination (now called action) may not - * always be a multiverse destination. It can be a command or server name as well. + * always be a multiverse destination. For example, it can be a command or server name as well. * Please see {@link MVPortal#getActionHandler()} instead. */ @Deprecated(since = "5.2", forRemoval = true) @@ -686,17 +693,19 @@ public boolean setDestination(DestinationInstance newDestination) { Logging.warning("Portal " + this.name + " is not set to use multiverse destination!"); return false; } - return this.configHandle.set(configNodes.action, newDestination.toString()).isSuccess(); + return setActionType("multiverse-destination") + .flatMap(ignore -> setAction(newDestination.toString())) + .isSuccess(); } /** * @deprecated Portals now have new types of action. Hence, the portal's destination (now called action) may not - * always be a multiverse destination. It can be a command or server name as well. + * always be a multiverse destination. For example, it can be a command or server name as well. * Please see {@link MVPortal#getActionHandler()} instead. */ @Deprecated(since = "5.2", forRemoval = true) @ApiStatus.ScheduledForRemoval(inVersion = "6.0") - public DestinationInstance getDestination() { + public @Nullable DestinationInstance getDestination() { return this.destinationsProvider.parseDestination(getAction()) .onFailure(f -> { if (getAction().equals("multiverse-destination")) { diff --git a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java index ca5ff090..69eb7288 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java @@ -81,6 +81,7 @@ public void registerHandlerType(@NotNull ActionHandlerType handlerType) { * * @since 5.2 */ + @ApiStatus.AvailableSince("5.2") public @NotNull Attempt, ActionFailureReason> parseHandler(@NotNull String actionType, @NotNull String action) { return parseHandler(Bukkit.getConsoleSender(), actionType, action); From 5f32afe5b172ee36b954580ca5e74ab333f50529 Mon Sep 17 00:00:00 2001 From: Ben Woo <30431861+benwoo1110@users.noreply.github.com> Date: Fri, 2 Jan 2026 23:55:48 +0800 Subject: [PATCH 10/10] Add null checks and update ``@NotNull` for action handler methods --- .../org/mvplugins/multiverse/portals/MVPortal.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java index 5ba6a310..1738caed 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java @@ -23,6 +23,7 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Entity; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mvplugins.multiverse.core.command.MVCommandManager; import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle; @@ -302,7 +303,8 @@ public PortalLocation getPortalLocation() { } @ApiStatus.AvailableSince("5.2") - public Try setActionType(ActionHandlerType actionType) { + public Try setActionType(@NotNull ActionHandlerType actionType) { + Objects.requireNonNull(actionType, "actionType cannot be null"); return configHandle.set(configNodes.actionType, actionType.getName()); } @@ -327,24 +329,24 @@ public String getAction() { } @ApiStatus.AvailableSince("5.2") - public Try setActionHandler(ActionHandler action) { + public Try setActionHandler(@NotNull ActionHandler action) { Objects.requireNonNull(action, "action cannot be null"); return setActionType(action.getHandlerType()) .flatMap(v -> setAction(action.serialise())); } @ApiStatus.AvailableSince("5.2") - public Attempt, ActionFailureReason> getActionHandler() { + public @NotNull Attempt, ActionFailureReason> getActionHandler() { return actionHandlerProvider.parseHandler(getActionType(), getAction()); } @ApiStatus.AvailableSince("5.2") - public Attempt, ActionFailureReason> getActionHandler(CommandSender sender) { + public @NotNull Attempt, ActionFailureReason> getActionHandler(@NotNull CommandSender sender) { return actionHandlerProvider.parseHandler(sender, getActionType(), getAction()); } @ApiStatus.AvailableSince("5.2") - public Attempt runActionFor(Entity entity) { + public @NotNull Attempt runActionFor(Entity entity) { return getActionHandler(entity) .mapAttempt(actionHandler -> actionHandler.runAction(this, entity)) .onSuccess(() -> {