diff --git a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java index 7c8e18fc..af1b4a31 100644 --- a/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java +++ b/src/main/java/org/mvplugins/multiverse/core/config/MVCoreConfig.java @@ -188,12 +188,8 @@ public class MVCoreConfig implements MVConfig { } @Override - @Nullable public String getJoinDestination() { - if (Objects.equals(configHandle.get(configNodes.JOIN_DESTINATION), "")) { - return null; - } - return configHandle.get(configNodes.JOIN_DESTINATION); + return configHandle.get(configNodes.JOIN_DESTINATION); } @Override diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java index dc9273f6..a69e4e89 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java @@ -17,7 +17,6 @@ import org.mvplugins.multiverse.core.world.WorldManager; public class MVChatListener implements InjectableListener { private final MVCoreConfig config; private final WorldManager worldManager; - private final MVPlayerListener playerListener; @Inject MVChatListener( @@ -26,7 +25,6 @@ public class MVChatListener implements InjectableListener { MVPlayerListener playerListener) { this.config = config; this.worldManager = worldManager; - this.playerListener = playerListener; } /** @@ -46,13 +44,7 @@ public class MVChatListener implements InjectableListener { return; } - String world = playerListener.getPlayerWorld().get(event.getPlayer().getName()); - if (world == null) { - world = event.getPlayer().getWorld().getName(); - playerListener.getPlayerWorld().put(event.getPlayer().getName(), world); - } - - String prefix = this.worldManager.getLoadedWorld(world) + String prefix = this.worldManager.getLoadedWorld(event.getPlayer().getWorld()) .map(mvworld -> mvworld.isHidden() ? "" : mvworld.getAlias()) .getOrElse(""); String chat = event.getFormat(); diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java index ecb28c01..01695594 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java @@ -7,17 +7,15 @@ package org.mvplugins.multiverse.core.listeners; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Objects; import com.dumptruckman.minecraft.util.Logging; import io.vavr.control.Option; import jakarta.inject.Inject; import jakarta.inject.Provider; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -37,7 +35,6 @@ import org.mvplugins.multiverse.core.api.BlockSafety; import org.mvplugins.multiverse.core.commandtools.MVCommandManager; import org.mvplugins.multiverse.core.config.MVCoreConfig; import org.mvplugins.multiverse.core.destination.DestinationsProvider; -import org.mvplugins.multiverse.core.destination.ParsedDestination; import org.mvplugins.multiverse.core.economy.MVEconomist; import org.mvplugins.multiverse.core.event.MVRespawnEvent; import org.mvplugins.multiverse.core.inject.InjectableListener; @@ -45,6 +42,7 @@ import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; import org.mvplugins.multiverse.core.teleportation.TeleportQueue; import org.mvplugins.multiverse.core.utils.result.ResultChain; import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.MultiverseWorld; import org.mvplugins.multiverse.core.world.WorldManager; import org.mvplugins.multiverse.core.world.entrycheck.EntryFeeResult; import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider; @@ -60,7 +58,6 @@ public class MVPlayerListener implements InjectableListener { private final Provider worldManagerProvider; private final BlockSafety blockSafety; private final AsyncSafetyTeleporter safetyTeleporter; - private final Server server; private final TeleportQueue teleportQueue; private final MVEconomist economist; private final WorldEntryCheckerProvider worldEntryCheckerProvider; @@ -68,8 +65,6 @@ public class MVPlayerListener implements InjectableListener { private final DestinationsProvider destinationsProvider; private final EnforcementHandler enforcementHandler; - private final Map playerWorld = new ConcurrentHashMap(); - @Inject MVPlayerListener( MultiverseCore plugin, @@ -77,7 +72,6 @@ public class MVPlayerListener implements InjectableListener { Provider worldManagerProvider, BlockSafety blockSafety, AsyncSafetyTeleporter safetyTeleporter, - Server server, TeleportQueue teleportQueue, MVEconomist economist, WorldEntryCheckerProvider worldEntryCheckerProvider, @@ -89,7 +83,6 @@ public class MVPlayerListener implements InjectableListener { this.worldManagerProvider = worldManagerProvider; this.blockSafety = blockSafety; this.safetyTeleporter = safetyTeleporter; - this.server = server; this.teleportQueue = teleportQueue; this.economist = economist; this.worldEntryCheckerProvider = worldEntryCheckerProvider; @@ -106,15 +99,6 @@ public class MVPlayerListener implements InjectableListener { return commandManagerProvider.get(); } - /** - * Gets the map of player and the world name they are in. - * - * @return the playerWorld-map - */ - public Map getPlayerWorld() { - return playerWorld; - } - /** * This method is called when a player respawns. * @@ -122,189 +106,182 @@ public class MVPlayerListener implements InjectableListener { */ @EventHandler(priority = EventPriority.LOW) public void playerRespawn(PlayerRespawnEvent event) { - World world = event.getPlayer().getWorld(); - LoadedMultiverseWorld mvWorld = getWorldManager().getLoadedWorld(world.getName()).getOrNull(); - // If it's not a World MV manages we stop. - if (mvWorld == null) { - return; - } - - if (mvWorld.getBedRespawn() && (event.isBedSpawn() || event.isAnchorSpawn())) { - Logging.fine("Spawning %s at their %s.", event.getPlayer().getName(), event.isBedSpawn() ? "BED" : "ANCHOR"); - return; - } - - // Get the instance of the World the player should respawn at. - LoadedMultiverseWorld respawnWorld = null; - if (getWorldManager().isLoadedWorld(mvWorld.getRespawnWorld())) { - respawnWorld = getWorldManager().getLoadedWorld(mvWorld.getRespawnWorld()).getOrNull(); - } - - // If it's null then it either means the World doesn't exist or the value is blank, so we don't handle it. - // NOW: We'll always handle it to get more accurate spawns - if (respawnWorld != null) { - world = respawnWorld.getBukkitWorld().getOrNull(); - } - // World has been set to the appropriate world - Location respawnLocation = getMostAccurateRespawnLocation(world); - - MVRespawnEvent respawnEvent = new MVRespawnEvent(respawnLocation, event.getPlayer(), "compatability"); - this.server.getPluginManager().callEvent(respawnEvent); - event.setRespawnLocation(respawnEvent.getPlayersRespawnLocation()); + getWorldManager() + .getLoadedWorld(event.getPlayer().getWorld()) + .filter(mvWorld -> isBedOrAnchorRespawn(event, mvWorld)) + .flatMap(MultiverseWorld::getRespawnWorld) + .map(this::getMostAccurateRespawnLocation) + .peek(respawnLocation -> callMVRepawnEvent(event, respawnLocation)); } - private Location getMostAccurateRespawnLocation(World w) { - LoadedMultiverseWorld mvw = getWorldManager().getLoadedWorld(w.getName()).getOrNull(); - if (mvw != null) { - return mvw.getSpawnLocation(); + private static boolean isBedOrAnchorRespawn(PlayerRespawnEvent event, LoadedMultiverseWorld mvWorld) { + if (mvWorld.getBedRespawn() && (event.isBedSpawn() || event.isAnchorSpawn())) { + Logging.fine("Spawning %s at their %s.", + event.getPlayer().getName(), + event.isBedSpawn() ? "BED" : "ANCHOR"); + return false; } - return w.getSpawnLocation(); + return true; + } + + private Location getMostAccurateRespawnLocation(World world) { + return getWorldManager().getLoadedWorld(world.getName()) + .map(LoadedMultiverseWorld::getSpawnLocation) + .getOrElse(world.getSpawnLocation()); + } + + private void callMVRepawnEvent(PlayerRespawnEvent event, Location respawnLocation) { + MVRespawnEvent respawnEvent = new MVRespawnEvent( + respawnLocation, event.getPlayer(), "compatability"); + Bukkit.getPluginManager().callEvent(respawnEvent); + event.setRespawnLocation(respawnEvent.getPlayersRespawnLocation()); } /** * This method is called when a player joins the server. + * * @param event The Event that was fired. */ @EventHandler public void playerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - LoadedMultiverseWorld world = getWorldManager().getLoadedWorld(player.getWorld()).getOrNull(); - if (world == null) { - Logging.finer("Player joined in a world that is not managed by Multiverse."); + if (player.hasPlayedBefore()) { + playerJoinAgain(event); + } else { + playerJoinFirstTime(event); + } + + // Handle the Players GameMode setting for the new world. + this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld()); + } + + private void playerJoinAgain(PlayerJoinEvent event) { + Logging.finer("Player joined AGAIN!"); + if (!config.getEnableJoinDestination()) { + return; + } + + Option.of(destinationsProvider.parseDestination(config.getJoinDestination())) + .peek(joinDestination -> { + Logging.fine("Moving player to: %s", joinDestination); + safetyTeleporter.teleportSafely(event.getPlayer(), joinDestination); + }) + .onEmpty(() -> { + Logging.warning("JoinDestination '%s' is invalid! Not teleporting player!", + config.getJoinDestination()); + }); + } + + private void playerJoinFirstTime(PlayerJoinEvent event) { + Logging.finer("Player joined for the FIRST time!"); + if (!config.getFirstSpawnOverride()) { return; } Option.of(destinationsProvider.parseDestination(config.getFirstSpawnLocation())) - .peek(parsedDestination -> { - if (!player.hasPlayedBefore()) { - Logging.finer("Player joined for the FIRST time!"); - if (config.getFirstSpawnOverride()) { - Logging.fine("Moving NEW player to(firstspawnoverride): %s", config.getFirstSpawnLocation()); - this.sendPlayerToDefaultWorld(player, parsedDestination); - } - } - handleJoinDestination(player); + .peek(firstSpawnDestination -> { + Logging.fine("Moving NEW player to(firstspawnoverride): %s", firstSpawnDestination); + safetyTeleporter.teleportSafely(event.getPlayer(), firstSpawnDestination); + }) + .onEmpty(() -> { + Logging.warning("FirstSpawnLocation '%s' is invalid! Not teleporting player!", + config.getFirstSpawnLocation()); }); - - // Handle the Players GameMode setting for the new world. - this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld()); - playerWorld.put(player.getName(), player.getWorld().getName()); - } - - /** - * Will teleport the player to the destination specified in config - * @param player The {@link Player} to teleport - */ - private void handleJoinDestination(@NotNull Player player) { - if (!config.getEnableJoinDestination()) { - Logging.finer("JoinDestination is disabled"); - // User has disabled the feature in config - return; - } - - if (config.getJoinDestination() == null) { - Logging.warning("Joindestination is enabled but no destination has been specified in config!"); - return; - } - - Logging.finer("JoinDestination is " + config.getJoinDestination()); - ParsedDestination joinDestination = destinationsProvider.parseDestination(config.getJoinDestination()); - - if (joinDestination == null) { - Logging.warning("The destination in JoinDestination in config is invalid"); - return; - } - - // Finally, teleport the player - safetyTeleporter.teleportSafely(player, player, joinDestination); } /** * This method is called when a player changes worlds. + * * @param event The Event that was fired. */ @EventHandler(priority = EventPriority.MONITOR) public void playerChangedWorld(PlayerChangedWorldEvent event) { // Permissions now determine whether or not to handle a gamemode. - this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld()); - playerWorld.put(event.getPlayer().getName(), event.getPlayer().getWorld().getName()); + handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld()); } /** * This method is called when a player teleports anywhere. + * * @param event The Event that was fired. */ - @EventHandler(priority = EventPriority.HIGHEST) + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void playerTeleport(PlayerTeleportEvent event) { - Logging.finer("Got teleport event for player '" - + event.getPlayer().getName() + "' with cause '" + event.getCause() + "'"); - if (event.isCancelled()) { + if (event.getTo() == null) { return; } + + Logging.finer("Got teleport event for player '%s' with cause '%s'", + event.getPlayer().getName(), event.getCause()); + Player teleportee = event.getPlayer(); - CommandSender teleporter; - Optional teleporterName = teleportQueue.popFromQueue(teleportee.getName()); - if (teleporterName.isPresent()) { - if (teleporterName.equals("CONSOLE")) { - Logging.finer("We know the teleporter is the console! Magical!"); - teleporter = this.server.getConsoleSender(); - } else { - teleporter = this.server.getPlayerExact(teleporterName.get()); - } - } else { - teleporter = teleportee; - } - Logging.finer("Inferred sender '" + teleporter + "' from name '" - + teleporterName + "', fetched from name '" + teleportee.getName() + "'"); - LoadedMultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld().getName()).getOrNull(); - LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld().getName()).getOrNull(); + CommandSender teleporter = getTeleporter(teleportee); + + World fromWorld = event.getFrom().getWorld(); + World toWorld = event.getTo().getWorld(); if (toWorld == null) { - Logging.fine("Player '" + teleportee.getName() + "' is teleporting to world '" - + event.getTo().getWorld().getName() + "' which is not managed by Multiverse-Core. No further " - + "actions will be taken by Multiverse-Core."); + Logging.warning("Player '%s' is teleporting to a world that does not exist. " + + "No further actions will be taken by Multiverse-Core.", teleportee.getName()); return; } - if (event.getFrom().getWorld().equals(event.getTo().getWorld())) { + + if (Objects.equals(fromWorld, toWorld)) { // The player is Teleporting to the same world. - Logging.finer("Player '" + teleportee.getName() + "' is teleporting to the same world."); - this.stateSuccess(teleportee.getName(), toWorld.getAlias()); + Logging.finer("Player '%s' is teleporting to the same world '%s'.", + teleportee.getName(), toWorld.getName()); return; } - ResultChain entryResult = worldEntryCheckerProvider.forSender(teleporter).canEnterWorld(fromWorld, toWorld) - .onSuccessReason(EntryFeeResult.Success.class, reason -> { - if (reason == EntryFeeResult.Success.ENOUGH_MONEY) { - economist.payEntryFee((Player) teleporter, toWorld); - // Send payment receipt - } - }) - .onFailure(results -> { - event.setCancelled(true); - getCommandManager().getCommandIssuer(teleporter).sendError(results.getLastResultMessage()); - }); - - Logging.fine("Teleport result: %s", entryResult); + checkTeleportEntry(event, toWorld, fromWorld, teleporter, teleportee); } - private void stateSuccess(String playerName, String worldName) { - Logging.fine("MV-Core is allowing Player '" + playerName - + "' to go to '" + worldName + "'."); + private @NotNull CommandSender getTeleporter(Player teleportee) { + return teleportQueue.popFromQueue(teleportee.getName()) + .map(teleporterName -> { + Logging.finer("Player '%s' is being teleported by '%s'.", + teleportee.getName(), teleporterName); + return teleporterName.equals("CONSOLE") + ? Bukkit.getConsoleSender() + : Bukkit.getPlayerExact(teleporterName); + }) + .getOrElse(teleportee); + } + + private void checkTeleportEntry( + PlayerTeleportEvent event, World toWorld, World fromWorld, CommandSender teleporter, Player teleportee) { + getWorldManager().getLoadedWorld(toWorld).peek(mvToWorld -> { + LoadedMultiverseWorld mvFromWorld = getWorldManager().getLoadedWorld(fromWorld).getOrNull(); + ResultChain entryResult = worldEntryCheckerProvider.forSender(teleporter) + .canEnterWorld(mvFromWorld, mvToWorld) + .onSuccessReason(EntryFeeResult.Success.class, reason -> { + if (teleporter instanceof Player player && reason == EntryFeeResult.Success.ENOUGH_MONEY) { + economist.payEntryFee(player, mvToWorld); + // Send payment receipt + } + }) + .onFailure(results -> { + event.setCancelled(true); + getCommandManager().getCommandIssuer(teleporter).sendError(results.getLastResultMessage()); + }); + Logging.fine("Teleport result: %s", entryResult); + }).onEmpty(() -> { + Logging.fine("Player '%s' is teleporting to world '%s' which is not managed by Multiverse-Core. " + + "No further actions will be taken by Multiverse-Core.", + teleportee.getName(), + toWorld.getName()); + }); } /** * This method is called to adjust the portal location to the actual portal location (and not * right outside of it. + * * @param event The Event that was fired. */ - @EventHandler(priority = EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void playerPortalCheck(PlayerPortalEvent event) { - if (event.isCancelled() || event.getFrom() == null) { - return; - } - - // REMEMBER! getTo MAY be NULL HERE!!! - // If the player was actually outside of the portal, adjust the from location - if (event.getFrom().getWorld().getBlockAt(event.getFrom()).getType() != Material.NETHER_PORTAL) { + World fromWorld = event.getFrom().getWorld(); + if (fromWorld != null && fromWorld.getBlockAt(event.getFrom()).getType() != Material.NETHER_PORTAL) { Location newloc = blockSafety.findPortalBlockNextTo(event.getFrom()); // TODO: Fix this. Currently, we only check for PORTAL blocks. I'll have to figure out what // TODO: we want to do here. @@ -312,71 +289,56 @@ public class MVPlayerListener implements InjectableListener { event.setFrom(newloc); } } - // Wait for the adjust, then return! - if (event.getTo() == null) { - return; - } } + /** * This method is called when a player actually portals via a vanilla style portal. + * * @param event The Event that was fired. */ - @EventHandler(priority = EventPriority.HIGH) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void playerPortal(PlayerPortalEvent event) { - if (event.isCancelled() || (event.getFrom() == null)) { - return; - } - // The adjust should have happened much earlier. if (event.getTo() == null) { return; } + if (config.isUsingCustomPortalSearch()) { event.setSearchRadius(config.getCustomPortalSearchRadius()); } - LoadedMultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld().getName()).getOrNull(); - LoadedMultiverseWorld toWorld = getWorldManager().getLoadedWorld(event.getTo().getWorld().getName()).getOrNull(); - if (event.getFrom().getWorld().equals(event.getTo().getWorld())) { - // The player is Portaling to the same world. + if (Objects.equals(event.getFrom().getWorld(), event.getTo().getWorld())) { Logging.finer("Player '" + event.getPlayer().getName() + "' is portaling to the same world."); return; } - ResultChain entryResult = worldEntryCheckerProvider.forSender(event.getPlayer()).canEnterWorld(fromWorld, toWorld) - .onFailure(results -> { - event.setCancelled(true); - getCommandManager().getCommandIssuer(event.getPlayer()).sendError(results.getLastResultMessage()); - }); - - Logging.fine("Teleport result: %s", entryResult); + checkPortalTeleportEntry(event); } - private void sendPlayerToDefaultWorld(final Player player, ParsedDestination parsedDestination) { - // Remove the player 1 tick after the login. I'm sure there's GOT to be a better way to do this... - this.server.getScheduler().scheduleSyncDelayedTask(this.plugin, - new Runnable() { - @Override - public void run() { - safetyTeleporter.teleportSafely(player, player, parsedDestination); - } - }, 1L); + private void checkPortalTeleportEntry(PlayerPortalEvent event) { + getWorldManager().getLoadedWorld(event.getTo().getWorld()).peek(toWorld -> { + LoadedMultiverseWorld fromWorld = getWorldManager().getLoadedWorld(event.getFrom().getWorld()).getOrNull(); + ResultChain entryResult = worldEntryCheckerProvider.forSender(event.getPlayer()) + .canEnterWorld(fromWorld, toWorld) + .onFailure(results -> { + event.setCancelled(true); + getCommandManager().getCommandIssuer(event.getPlayer()) + .sendError(results.getLastResultMessage()); + }); + Logging.fine("Teleport result: %s", entryResult); + }); } /** - * Handles the gamemode for the specified {@link Player}. + * Handles the gamemode for the specified {@link Player} with a 1 tick delay. * * @param player The {@link Player}. * @param world The {@link World} the player is supposed to be in. */ private void handleGameModeAndFlight(final Player player, World world) { - // We perform this task one tick later to MAKE SURE that the player actually reaches the - // destination world, otherwise we'd be changing the player mode if they havent moved anywhere. - this.server.getScheduler().scheduleSyncDelayedTask(this.plugin, () -> { - if (!player.isOnline() || !player.getWorld().equals(world)) { - return; - } - enforcementHandler.handleFlightEnforcement(player); - enforcementHandler.handleGameModeEnforcement(player); - }, 1L); + if (!player.isOnline() || !player.getWorld().equals(world)) { + return; + } + enforcementHandler.handleFlightEnforcement(player); + enforcementHandler.handleGameModeEnforcement(player); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java index 99077226..c3edcc97 100644 --- a/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java +++ b/src/main/java/org/mvplugins/multiverse/core/teleportation/TeleportQueue.java @@ -2,9 +2,9 @@ package org.mvplugins.multiverse.core.teleportation; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import com.dumptruckman.minecraft.util.Logging; +import io.vavr.control.Option; import org.jvnet.hk2.annotations.Service; @Service @@ -29,15 +29,16 @@ public class TeleportQueue { /** * This method is used to find out who is teleporting a player. + * * @param playerName The teleported player (the teleportee). * @return The player that teleported the other one (the teleporter). */ - public Optional popFromQueue(String playerName) { + public Option popFromQueue(String playerName) { if (teleportQueue.containsKey(playerName)) { String teleportee = teleportQueue.get(playerName); teleportQueue.remove(playerName); - return Optional.of(teleportee); + return Option.of(teleportee); } - return Optional.empty(); + return Option.none(); } } diff --git a/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java b/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java index 75b9cd58..78f9aacf 100644 --- a/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java +++ b/src/main/java/org/mvplugins/multiverse/core/world/MultiverseWorld.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.List; import com.google.common.base.Strings; +import io.vavr.control.Option; import io.vavr.control.Try; import org.bukkit.Bukkit; import org.bukkit.Difficulty; @@ -463,8 +464,8 @@ public class MultiverseWorld { * * @return A world that exists on the server. */ - public @Nullable World getRespawnWorld() { - return Bukkit.getWorld(worldConfig.getRespawnWorld()); + public @Nullable Option getRespawnWorld() { + return Option.of(Bukkit.getWorld(worldConfig.getRespawnWorld())); } /**