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..65022709 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortal.java @@ -9,16 +9,22 @@ 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.Bukkit; +import org.bukkit.command.CommandSender; 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 +33,16 @@ 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.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; 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 +75,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 +109,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 +142,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 +168,10 @@ private MVPortal(MultiversePortals plugin, String name) { } } + /** + * + * @return + */ public String getName() { return this.name; } @@ -271,24 +301,53 @@ public PortalLocation getPortalLocation() { return this.location; } - public boolean setDestination(String destinationString) { - DestinationInstance newDestination = this.destinationsProvider.parseDestination(destinationString).getOrNull(); - return setDestination(newDestination); + @ApiStatus.AvailableSince("5.2") + 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(); + @ApiStatus.AvailableSince("5.2") + 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(); + @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(entity) + .mapAttempt(actionHandler -> actionHandler.runAction(this, entity)) + .onSuccess(() -> { + if (entity instanceof Player player) { + plugin.getPortalSession(player).setTeleportTime(new Date()); + } + }) + .onFailure(failure -> + Logging.warning(ChatTextFormatter.removeColor("Invalid Portal Action: " + + failure.getFailureMessage().formatted(commandManager.getCommandIssuer(Bukkit.getConsoleSender()))))); } /** @@ -598,4 +657,52 @@ public boolean isExempt(Player player) { 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) { + DestinationInstance newDestination = this.destinationsProvider.parseDestination(destinationString).getOrNull(); + 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) { + 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 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() { + 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..34ef7e61 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java +++ b/src/main/java/org/mvplugins/multiverse/portals/MVPortalNodes.java @@ -6,10 +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.destination.DestinationInstance; 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.utils.MultiverseRegion; import java.util.Collections; @@ -21,12 +22,12 @@ 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 +91,23 @@ 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.getAllHandlerTypeNames()) + .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())) + .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/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/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 new file mode 100644 index 00000000..8982d595 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionFailureReason.java @@ -0,0 +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 new file mode 100644 index 00000000..a3a0d4da --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandler.java @@ -0,0 +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; + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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 new file mode 100644 index 00000000..ca5ff090 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerProvider.java @@ -0,0 +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<>(); + + /** + * 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); + } + + /** + * 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(); + } + + /** + * 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 + "'. Supported types are: " + + String.join(", ", handlerTypeMap.keySet())))); + } + + /** + * 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(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 new file mode 100644 index 00000000..3a3c3130 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/ActionHandlerType.java @@ -0,0 +1,76 @@ +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.ApiStatus; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; + +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; + + /** + * 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; + } + + /** + * 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; + } + + /** + * 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); + + /** + * 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/action/types/BungeeServerList.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java new file mode 100644 index 00000000..a7e7cf5e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/BungeeServerList.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.portals.action.types; + +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.Collection; +import java.util.Collections; +import java.util.List; + +@Service +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); + } + + Collection 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 + 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/action/types/CommandActionHandler.java b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java new file mode 100644 index 00000000..fae95706 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandler.java @@ -0,0 +1,37 @@ +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; + +final class CommandActionHandler extends ActionHandler { + + private final CommandRunner commandRunner; + + CommandActionHandler(CommandActionHandlerType handlerType, CommandRunner commandRunner) { + super(handlerType); + this.commandRunner = commandRunner; + } + + @Override + 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 new file mode 100644 index 00000000..c8b9f6b0 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandActionHandlerType.java @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.portals.action.types; + +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 { + + @Inject + CommandActionHandlerType() { + super("command"); + } + + @Override + 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 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 new file mode 100644 index 00000000..6161688f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/CommandRunner.java @@ -0,0 +1,94 @@ +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(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; + + private CommandRunner(String rawCmd, String cmdStr, String cmdType) { + this.rawCmd = rawCmd; + this.cmdStr = cmdStr; + this.cmdType = cmdType; + } + + void runCommand(CommandSender sender) { + runCommand(sender, parseCmdStrPlaceholders(sender)); + } + + String parseCmdStrPlaceholders(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 rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "myself"); + } + + @Override + protected void runCommand(CommandSender sender, String cmd) { + Bukkit.dispatchCommand(sender, cmd); + } + } + + private static class Op extends CommandRunner { + + private Op(String rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "as operator"); + } + + @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 rawCmd, String cmdStr) { + super(rawCmd, cmdStr, "from console"); + } + + @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..5aab7da1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandler.java @@ -0,0 +1,61 @@ +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; + +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 @NotNull Attempt runAction(@NotNull MVPortal portal, @NotNull 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); + } + + @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 new file mode 100644 index 00000000..81b34820 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/MultiverseDestinationActionHandlerType.java @@ -0,0 +1,46 @@ +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.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; +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 @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 @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 new file mode 100644 index 00000000..b19d8692 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandler.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.portals.action.types; + +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; +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 @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!")); + } + 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."))); + } + + @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 new file mode 100644 index 00000000..9748a9f2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/portals/action/types/ServerActionHandlerType.java @@ -0,0 +1,42 @@ +package org.mvplugins.multiverse.portals.action.types; + +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.MultiversePortals; +import org.mvplugins.multiverse.portals.action.ActionFailureReason; +import org.mvplugins.multiverse.portals.action.ActionHandlerType; + +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 @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 @NotNull Collection suggestActions(@NotNull CommandSender sender, @NotNull 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..30c0633f 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 @@ -67,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/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..4c2ba8f8 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityMoveListener.java @@ -1,14 +1,12 @@ 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; 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; @@ -18,19 +16,16 @@ @Service public final class MVPEntityMoveListener implements Listener { - private final PlayerListenerHelper helper; + private final PortalListenerHelper helper; private final PortalManager portalManager; - private final AsyncSafetyTeleporter teleporter; private final PortalsConfig portalsConfig; @Inject - MVPEntityMoveListener(@NotNull PlayerListenerHelper helper, + MVPEntityMoveListener(@NotNull PortalListenerHelper helper, @NotNull PortalManager portalManager, - @NotNull AsyncSafetyTeleporter teleporter, @NotNull PortalsConfig portalsConfig) { this.helper = helper; this.portalManager = portalManager; - this.teleporter = teleporter; this.portalsConfig = portalsConfig; } @@ -50,14 +45,8 @@ 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); + 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 4a896b63..2eb9ee8f 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPEntityPortalListener.java @@ -1,30 +1,36 @@ 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; 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.core.teleportation.BlockSafety; 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 MVPEntityPortalListener implements Listener { private final PortalManager portalManager; - private final AsyncSafetyTeleporter teleporter; + private final PortalsConfig portalsConfig; + private final BlockSafety blockSafety; + private final PortalListenerHelper helper; @Inject MVPEntityPortalListener(@NotNull PortalManager portalManager, - @NotNull AsyncSafetyTeleporter teleporter) { + @NotNull PortalsConfig portalsConfig, + @NotNull BlockSafety blockSafety, + @NotNull PortalListenerHelper helper) { this.portalManager = portalManager; - this.teleporter = teleporter; + this.portalsConfig = portalsConfig; + this.blockSafety = blockSafety; + this.helper = helper; } @EventHandler(ignoreCancelled = true) @@ -33,20 +39,28 @@ 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); + 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 256b9b83..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,29 +25,21 @@ 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 WorldManager worldManager; - private final AsyncSafetyTeleporter teleporter; + private final PortalListenerHelper helper; @Inject MVPPlayerMoveListener( @NotNull MultiversePortals plugin, @NotNull PortalsConfig portalsConfig, - @NotNull PlayerListenerHelper helper, - @NotNull WorldManager worldManager, - @NotNull AsyncSafetyTeleporter teleporter) { + @NotNull PortalListenerHelper helper) { this.plugin = plugin; this.portalsConfig = portalsConfig; this.helper = helper; - this.worldManager = worldManager; - this.teleporter = teleporter; } @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) @@ -77,23 +65,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; @@ -102,20 +75,13 @@ 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(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 +90,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..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,26 +25,20 @@ 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 AsyncSafetyTeleporter teleporter; + 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 AsyncSafetyTeleporter teleporter) { + @NotNull PortalListenerHelper 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,58 +64,32 @@ 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."); + event.setCancelled(true); 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!"); + PortalPlayerSession ps = this.plugin.getPortalSession(player); + ps.setStaleLocation(playerPortalLoc, MoveType.PLAYER_MOVE); + if (ps.showDebugInfo()) { + event.setCancelled(true); 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."); return; } - PlayerListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); + PortalListenerHelper.PortalUseResult portalUseResult = helper.checkPlayerCanUsePortal(portal, player); if (!portalUseResult.canUse()) { 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 +100,11 @@ 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); + helper.stateSuccess(player.getDisplayName(), portal.getName()); + 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..561d9a31 100644 --- a/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java +++ b/src/main/java/org/mvplugins/multiverse/portals/listeners/MVPVehicleListener.java @@ -8,13 +8,12 @@ package org.mvplugins.multiverse.portals.listeners; import java.util.ArrayList; +import java.util.Date; import java.util.List; 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; @@ -34,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; } @@ -90,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()); @@ -101,14 +97,10 @@ 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); + 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 a50cd199..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,69 +1,114 @@ 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[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!"); + 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()) { + issuer.sendMessage(ChatColor.WHITE + "Configured Action: " + ChatColor.RED + ChatColor.ITALIC + "NOT SET!"); } 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; + issuer.sendMessage(ChatColor.WHITE + "Configured Action: " + ChatColor.GOLD + portal.getAction()); + } + 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] : ""; + 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']