package tc.oc.commons.bukkit.ticket; import java.util.Comparator; import java.util.Objects; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import tc.oc.api.bukkit.users.BukkitUserStore; import tc.oc.api.docs.Arena; import tc.oc.api.docs.Game; import tc.oc.api.docs.PlayerId; import tc.oc.api.docs.Server; import tc.oc.api.docs.Ticket; import tc.oc.api.docs.virtual.MatchDoc; import tc.oc.api.docs.virtual.ServerDoc; import tc.oc.api.games.ArenaStore; import tc.oc.api.games.GameStore; import tc.oc.api.games.TicketService; import tc.oc.api.games.TicketStore; import tc.oc.api.message.types.PlayGameRequest; import tc.oc.api.message.types.Reply; import tc.oc.minecraft.scheduler.SyncExecutor; import tc.oc.api.servers.ServerStore; import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.WarningComponent; import tc.oc.commons.bukkit.format.GameFormatter; import tc.oc.commons.bukkit.teleport.PlayerServerChanger; import tc.oc.commons.bukkit.util.PlayerStates; import tc.oc.commons.core.chat.Audience; import tc.oc.commons.core.commands.CommandFutureCallback; /** * User actions for querying, joining, and leaving {@link Game}s */ @Singleton public class TicketBooth { private final SyncExecutor syncExecutor; private final BukkitUserStore userStore; private final PlayerStates playerStates; private final Audiences audiences; private final TicketService ticketService; private final GameFormatter gameFormatter; private final GameStore games; private final ArenaStore arenas; private final TicketStore tickets; private final ServerStore servers; private final PlayerServerChanger serverChanger; private final Server localServer; private @Nullable PlayHandler playHandler; @Inject TicketBooth(SyncExecutor syncExecutor, BukkitUserStore userStore, PlayerStates playerStates, Audiences audiences, TicketService ticketService, GameFormatter gameFormatter, GameStore games, ArenaStore arenas, TicketStore tickets, ServerStore servers, PlayerServerChanger serverChanger, Server localServer) { this.syncExecutor = syncExecutor; this.userStore = userStore; this.playerStates = playerStates; this.audiences = audiences; this.ticketService = ticketService; this.gameFormatter = gameFormatter; this.games = games; this.arenas = arenas; this.tickets = tickets; this.servers = servers; this.serverChanger = serverChanger; this.localServer = localServer; } @FunctionalInterface public interface PlayHandler { boolean requestPlay(Player player); } public PlayHandler playHandler() { return playHandler; } public void setPlayHandler(PlayHandler handler) { playHandler = handler; } public void removePlayHandler(PlayHandler handler) { if(handler.equals(playHandler)) { playHandler = null; } } public Set<Game> allGames(CommandSender viewer) { return Sets.filter(games.set(), game -> game.visibility() == ServerDoc.Visibility.PUBLIC); } public void showGames(CommandSender sender) { gameFormatter.sendList(audiences.get(sender), allGames(sender)); } public @Nullable Arena localArena() { final String id = localServer.arena_id(); return id == null ? null : arenas.byId(id); } public @Nullable Game localGame() { final String id = localServer.game_id(); return id == null ? null : games.byId(id); } public @Nullable Arena currentArena(Player player) { return currentArena(userStore.playerId(player)); } public @Nullable Game currentGame(Player player) { return currentGame(userStore.playerId(player)); } public @Nullable Arena currentArena(PlayerId playerId) { final Ticket ticket = tickets.tryUser(playerId); return ticket == null ? null : arenas.byId(ticket.arena_id()); } public @Nullable Game currentGame(PlayerId playerId) { final Arena arena = currentArena(playerId); return arena == null ? null : games.byId(arena.game_id()); } public @Nullable Game findGame(CommandSender sender, String name) { name = name.trim().toLowerCase(); for(Game game : games.set()) { if(game.visibility() != ServerDoc.Visibility.PRIVATE && name.equals(game.name().toLowerCase())) { return game; } } audiences.get(sender).sendMessage(new WarningComponent("game.unknown", name)); showGames(sender); return null; } public @Nullable Arena findArena(CommandSender sender, @Nullable String name) { final Arena arena; if(name == null || name.length() == 0) { arena = localArena(); if(arena == null) { showGames(sender); } } else { final Game game = findGame(sender, name); if(game == null) return null; arena = arenas.tryDatacenterAndGameId(localServer.datacenter(), game._id()); if(arena == null) { audiences.get(sender).sendMessage(new WarningComponent("game.offline", gameFormatter.name(game))); } } return arena; } private ListenableFuture<Reply> sendPlayRequest(Player player, @Nullable Arena arena) { return sendPlayRequest(userStore.playerId(player), arena); } private ListenableFuture<Reply> sendPlayRequest(PlayerId playerId, @Nullable Arena arena) { return sendPlayRequest(playerId, arena, false); } private ListenableFuture<Reply> sendPlayRequest(PlayerId playerId, @Nullable Arena arena, boolean force) { final Arena playing = currentArena(playerId); if(!force && Objects.equals(playing, arena)) { return Futures.immediateFuture(Reply.SUCCESS); } else { return ticketService.requestPlay(new PlayGameRequest() { @Override public String user_id() { return playerId._id(); } @Override public @Nullable String arena_id() { return arena == null ? null : arena._id(); } }); } } public void leaveGame(Player player, boolean returnToLobby) { final Audience audience = audiences.get(player); final PlayerId playerId = userStore.playerId(player); final Game game = currentGame(playerId); if(game == null) { audience.sendMessage(gameFormatter.notPlaying()); return; } syncExecutor.callback( sendPlayRequest(playerId, null), CommandFutureCallback.onSuccess(player, reply -> { audience.sendMessage(gameFormatter.left(game)); if(returnToLobby) { serverChanger.sendPlayerToLobby(player, true); } }) ); } public void playLocalGame(Player player) { final Game game = localGame(); if(game != null) { final Arena arena = arenas.tryDatacenterAndGameId(localServer.datacenter(), game._id()); if(arena != null) { playGame(player, arena); } } else if(playHandler != null) { playHandler.requestPlay(player); } else { showGames(player); } } public void playGame(Player player, @Nullable String name) { final Arena arena = findArena(player, name); if(arena != null) { playGame(player, arena); } } public void playGame(Player player, Arena arena) { final PlayerId playerId = userStore.playerId(player); boolean forceRequest = false; if(arena.equals(currentArena(playerId))) { final Game game = games.byId(arena.game_id()); final Audience audience = audiences.get(player); final MatchDoc match = localServer.current_match(); if(match != null && match.join_mid_match()) { audience.sendMessage(gameFormatter.alreadyPlaying(game)); return; } else { audience.sendMessage(gameFormatter.replay(game)); forceRequest = playerStates.isObserving(player); } } sendPlayRequest(playerId, arena, forceRequest); } public void watchGame(Player player, @Nullable String name) { final Arena arena = findArena(player, name); if(arena != null) { watchGame(player, arena); } } public void watchGame(Player player, Arena arena) { final Audience audience = audiences.get(player); final Game game = games.byId(arena.game_id()); final Optional<Server> fullest = servers.byArena(arena) .stream() .filter(Server::online) .max(Comparator.comparing(Server::num_participating)); if(!fullest.isPresent()) { audience.sendWarning(new TranslatableComponent("game.offline", gameFormatter.name(game)), false); return; } else if(fullest.get().num_participating() < 2) { audience.sendWarning(new TranslatableComponent("game.empty", gameFormatter.name(game)), false); return; } syncExecutor.callback( sendPlayRequest(player, null), reply -> { serverChanger.sendPlayerToServer(player, fullest.get(), false); } ); } }