package tc.oc.commons.bukkit.teleport; import java.time.Duration; import java.time.Instant; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Singleton; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import tc.oc.api.bukkit.users.OnlinePlayers; import tc.oc.api.message.MessageListener; import tc.oc.api.message.MessageQueue; import tc.oc.api.message.types.PlayerTeleportRequest; import tc.oc.commons.bukkit.permissions.PermissionRegistry; import tc.oc.commons.core.logging.Loggers; import tc.oc.commons.core.plugin.PluginFacet; import tc.oc.minecraft.api.scheduler.Scheduler; import tc.oc.minecraft.api.scheduler.Tickable; import tc.oc.minecraft.scheduler.SyncExecutor; /** * Listens for remote teleport requests and executes both ends of them. * * When the request target is a different server, this class sends the * player there (via Bungee plugin channel). We do this in Bukkit so that * it can be cancelled i.e. by PGM when the player is in a match. * * When the target is this server, and there is also a target player, * this class waits for the teleporting player to arrive and then * teleports them to the target. */ @Singleton public class TeleportListener implements MessageListener, Listener, PluginFacet, Tickable { private static final Duration TIMEOUT = Duration.ofSeconds(10); static class Received { final java.time.Instant timestamp = java.time.Instant.now(); final PlayerTeleportRequest request; Received(PlayerTeleportRequest request) { this.request = request; } } private final Logger logger; private final MessageQueue primaryQueue; private final Teleporter teleporter; private final SyncExecutor syncExecutor; private final Scheduler scheduler; private final PermissionRegistry permissionRegistry; private final OnlinePlayers onlinePlayers; private final Map<UUID, Received> requests = new HashMap<>(); @Inject TeleportListener(Loggers loggers, MessageQueue primaryQueue, Teleporter teleporter, SyncExecutor syncExecutor, Scheduler scheduler, PermissionRegistry permissionRegistry, OnlinePlayers onlinePlayers) { this.logger = loggers.get(getClass()); this.primaryQueue = primaryQueue; this.onlinePlayers = onlinePlayers; this.syncExecutor = syncExecutor; this.scheduler = scheduler; this.permissionRegistry = permissionRegistry; this.teleporter = teleporter; } @Override public Duration tickPeriod() { return TIMEOUT; } @Override public void enable() { permissionRegistry.addPermission(Teleporter.PERMISSION); primaryQueue.subscribe(this, syncExecutor); primaryQueue.bind(PlayerTeleportRequest.class); } @Override public void disable() { primaryQueue.unsubscribe(this); } @Override public void tick() { final Instant now = Instant.now(); requests.values().removeIf(received -> received.timestamp.plus(TIMEOUT).isBefore(now)); } @HandleMessage public void onTeleport(PlayerTeleportRequest request) { Player traveler = onlinePlayers.find(request.player_uuid); if(!teleporter.isLocal(request.target_server())) { // Send player to another server if(traveler != null && traveler.hasPermission(Teleporter.PERMISSION)) { if(request.target_server() == null) { logger.info("Sending " + traveler.getName() + " to lobby"); } else { logger.info("Sending " + traveler.getName() + " to server " + request.target_server().bungee_name()); } teleporter.remoteTeleport(traveler, request.target_server()); } } else if(request.target_player_uuid != null) { // Teleport player to a target on this server if(traveler != null) { doTeleport(traveler, request); } else { queueTeleport(request); } } } /** * Priority must be high enough so that whatever applies observer permissions * runs before this, since those include the teleport permission. Lobby does it * in PlayerListener and PGM does it in SpawnMatchModule. */ @EventHandler(priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent event) { Received received = requests.remove(event.getPlayer().getUniqueId()); if(received != null) { doTeleport(event.getPlayer(), received.request); } } private void queueTeleport(final PlayerTeleportRequest request) { logger.info("Queueing remote teleport for offline player " + request.player_uuid); requests.put(request.player_uuid, new Received(request)); scheduler.delaySync(TIMEOUT, () -> { if(requests.remove(request.player_uuid) != null) { logger.warning("Expired remote teleport for " + request.player_uuid); } }); } private void doTeleport(Player traveler, PlayerTeleportRequest request) { teleporter.localTeleport(traveler, request.target_player_uuid); } }