summaryrefslogtreewikicommitdiff
path: root/src/main/java/dev/tylerm/khs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/dev/tylerm/khs')
-rw-r--r--src/main/java/dev/tylerm/khs/Main.java301
-rw-r--r--src/main/java/dev/tylerm/khs/command/Confirm.java62
-rw-r--r--src/main/java/dev/tylerm/khs/command/Help.java87
-rw-r--r--src/main/java/dev/tylerm/khs/command/Join.java49
-rw-r--r--src/main/java/dev/tylerm/khs/command/Leave.java49
-rw-r--r--src/main/java/dev/tylerm/khs/command/Reload.java55
-rw-r--r--src/main/java/dev/tylerm/khs/command/Send.java68
-rw-r--r--src/main/java/dev/tylerm/khs/command/SetExitLocation.java42
-rw-r--r--src/main/java/dev/tylerm/khs/command/Start.java77
-rw-r--r--src/main/java/dev/tylerm/khs/command/Stop.java46
-rw-r--r--src/main/java/dev/tylerm/khs/command/Top.java72
-rw-r--r--src/main/java/dev/tylerm/khs/command/Wins.java67
-rw-r--r--src/main/java/dev/tylerm/khs/command/location/LocationUtils.java52
-rw-r--r--src/main/java/dev/tylerm/khs/command/location/Locations.java17
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/Add.java53
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/Debug.java122
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/GoTo.java62
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/List.java45
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/Remove.java52
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/Save.java83
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/Status.java79
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/blockhunt/Enabled.java63
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Add.java75
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/List.java58
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Remove.java75
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/set/Border.java86
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/set/Bounds.java112
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/set/Lobby.java42
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/set/SeekerLobby.java56
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/set/Spawn.java67
-rw-r--r--src/main/java/dev/tylerm/khs/command/map/unset/Border.java58
-rw-r--r--src/main/java/dev/tylerm/khs/command/util/CommandGroup.java186
-rw-r--r--src/main/java/dev/tylerm/khs/command/util/ICommand.java20
-rw-r--r--src/main/java/dev/tylerm/khs/command/world/Create.java80
-rw-r--r--src/main/java/dev/tylerm/khs/command/world/Delete.java74
-rw-r--r--src/main/java/dev/tylerm/khs/command/world/List.java55
-rw-r--r--src/main/java/dev/tylerm/khs/command/world/Tp.java48
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Config.java289
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/ConfigManager.java331
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Items.java220
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Leaderboard.java51
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Localization.java80
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/LocalizationString.java37
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Map.java241
-rw-r--r--src/main/java/dev/tylerm/khs/configuration/Maps.java158
-rw-r--r--src/main/java/dev/tylerm/khs/database/Database.java107
-rw-r--r--src/main/java/dev/tylerm/khs/database/GameDataTable.java221
-rw-r--r--src/main/java/dev/tylerm/khs/database/InventoryTable.java103
-rw-r--r--src/main/java/dev/tylerm/khs/database/LegacyTable.java101
-rw-r--r--src/main/java/dev/tylerm/khs/database/NameDataTable.java113
-rw-r--r--src/main/java/dev/tylerm/khs/database/connections/DatabaseConnection.java29
-rw-r--r--src/main/java/dev/tylerm/khs/database/connections/MySQLConnection.java65
-rw-r--r--src/main/java/dev/tylerm/khs/database/connections/SQLiteConnection.java64
-rw-r--r--src/main/java/dev/tylerm/khs/database/util/LegacyPlayerInfo.java50
-rw-r--r--src/main/java/dev/tylerm/khs/database/util/PlayerInfo.java84
-rw-r--r--src/main/java/dev/tylerm/khs/game/Board.java492
-rw-r--r--src/main/java/dev/tylerm/khs/game/Disguiser.java74
-rw-r--r--src/main/java/dev/tylerm/khs/game/EntityHider.java326
-rw-r--r--src/main/java/dev/tylerm/khs/game/Game.java411
-rw-r--r--src/main/java/dev/tylerm/khs/game/PlayerLoader.java187
-rw-r--r--src/main/java/dev/tylerm/khs/game/events/Border.java72
-rw-r--r--src/main/java/dev/tylerm/khs/game/events/Glow.java72
-rw-r--r--src/main/java/dev/tylerm/khs/game/events/Taunt.java101
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/BlockedCommandHandler.java36
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/ChatHandler.java20
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/DamageHandler.java136
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/DisguiseHandler.java101
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/InteractHandler.java184
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/InventoryHandler.java145
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/JoinLeaveHandler.java98
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/MovementHandler.java61
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/PlayerHandler.java56
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/RespawnHandler.java40
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/WorldInteractHandler.java48
-rw-r--r--src/main/java/dev/tylerm/khs/game/listener/events/PlayerJumpEvent.java56
-rw-r--r--src/main/java/dev/tylerm/khs/game/util/CountdownDisplay.java26
-rw-r--r--src/main/java/dev/tylerm/khs/game/util/Disguise.java219
-rw-r--r--src/main/java/dev/tylerm/khs/game/util/Status.java7
-rw-r--r--src/main/java/dev/tylerm/khs/game/util/WinType.java7
-rw-r--r--src/main/java/dev/tylerm/khs/util/Location.java151
-rw-r--r--src/main/java/dev/tylerm/khs/util/PAPIExpansion.java174
-rw-r--r--src/main/java/dev/tylerm/khs/util/Pair.java21
-rw-r--r--src/main/java/dev/tylerm/khs/util/Tuple.java27
-rw-r--r--src/main/java/dev/tylerm/khs/util/packet/AbstractPacket.java29
-rw-r--r--src/main/java/dev/tylerm/khs/util/packet/BlockChangePacket.java24
-rw-r--r--src/main/java/dev/tylerm/khs/util/packet/EntityMetadataPacket.java69
-rw-r--r--src/main/java/dev/tylerm/khs/util/packet/EntityTeleportPacket.java29
-rw-r--r--src/main/java/dev/tylerm/khs/world/VoidGenerator.java55
-rw-r--r--src/main/java/dev/tylerm/khs/world/WorldLoader.java155
89 files changed, 8748 insertions, 0 deletions
diff --git a/src/main/java/dev/tylerm/khs/Main.java b/src/main/java/dev/tylerm/khs/Main.java
new file mode 100644
index 0000000..bb69c99
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/Main.java
@@ -0,0 +1,301 @@
+package dev.tylerm.khs;
+
+import dev.tylerm.khs.command.*;
+import dev.tylerm.khs.command.map.Debug;
+import dev.tylerm.khs.command.map.GoTo;
+import dev.tylerm.khs.command.map.Save;
+import dev.tylerm.khs.command.map.blockhunt.blocks.Add;
+import dev.tylerm.khs.command.map.blockhunt.blocks.List;
+import dev.tylerm.khs.command.map.blockhunt.blocks.Remove;
+import dev.tylerm.khs.command.map.set.*;
+import dev.tylerm.khs.configuration.*;
+import dev.tylerm.khs.game.*;
+import dev.tylerm.khs.game.listener.*;
+import dev.tylerm.khs.game.util.Status;
+import dev.tylerm.khs.util.PAPIExpansion;
+import dev.tylerm.khs.command.map.blockhunt.Enabled;
+import dev.tylerm.khs.command.world.Create;
+import dev.tylerm.khs.command.world.Delete;
+import dev.tylerm.khs.command.world.Tp;
+import dev.tylerm.khs.database.Database;
+import dev.tylerm.khs.command.util.CommandGroup;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Listener;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Main extends JavaPlugin implements Listener {
+
+ private static Main instance;
+ private static int version;
+ private static int sub_version;
+
+ private Database database;
+ private Board board;
+ private Disguiser disguiser;
+ private EntityHider entityHider;
+ private Game game;
+ private CommandGroup commandGroup;
+ private boolean loaded;
+
+ public void onEnable() {
+
+ long start = System.currentTimeMillis();
+
+ getLogger().info("Loading Kenshin's Hide and Seek");
+ Main.instance = this;
+
+ getLogger().info("Getting minecraft version...");
+ this.updateVersion();;
+
+ try {
+ getLogger().info("Loading config.yml...");
+ Config.loadConfig();
+ getLogger().info("Loading maps.yml...");
+ Maps.loadMaps();
+ getLogger().info("Loading localization.yml...");
+ Localization.loadLocalization();
+ getLogger().info("Loading items.yml...");
+ Items.loadItems();
+ getLogger().info("Loading leaderboard.yml...");
+ Leaderboard.loadLeaderboard();
+ } catch (Exception e) {
+ getLogger().severe(e.getMessage());
+ Bukkit.getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ getLogger().info("Creating internal scoreboard...");
+ this.board = new Board();
+ getLogger().info("Connecting to database...");
+ this.database = new Database();
+ getLogger().info("Loading disguises...");
+ this.disguiser = new Disguiser();
+ getLogger().info("Loading entity hider...");
+ this.entityHider = new EntityHider(this, EntityHider.Policy.BLACKLIST);
+ getLogger().info("Registering listeners...");
+ this.registerListeners();
+
+ getLogger().info("Registering commands...");
+ this.commandGroup = new CommandGroup("hs",
+ new Help(),
+ new Reload(),
+ new Join(),
+ new Leave(),
+ new Send(),
+ new Start(),
+ new Stop(),
+ new CommandGroup("map",
+ new CommandGroup("blockhunt",
+ new CommandGroup("blocks",
+ new Add(),
+ new Remove(),
+ new List()
+ ),
+ new Enabled()
+ ),
+ new CommandGroup("set",
+ new Lobby(),
+ new Spawn(),
+ new SeekerLobby(),
+ new Border(),
+ new Bounds()
+ ),
+ new CommandGroup("unset",
+ new dev.tylerm.khs.command.map.unset.Border()
+ ),
+ new dev.tylerm.khs.command.map.Add(),
+ new dev.tylerm.khs.command.map.Remove(),
+ new dev.tylerm.khs.command.map.List(),
+ new dev.tylerm.khs.command.map.Status(),
+ new Save(),
+ new Debug(),
+ new GoTo()
+ ),
+ new CommandGroup("world",
+ new Create(),
+ new Delete(),
+ new dev.tylerm.khs.command.world.List(),
+ new Tp()
+ ),
+ new SetExitLocation(),
+ new Top(),
+ new Wins(),
+ new Confirm()
+ );
+
+ getLogger().info("Loading game...");
+ game = new Game(null, board);
+
+ getLogger().info("Scheduling tick tasks...");
+ getServer().getScheduler().runTaskTimer(this, this::onTick,0,1).getTaskId();
+
+ getLogger().info("Registering outgoing bungeecord plugin channel...");
+ Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
+
+ getLogger().info("Checking for PlaceholderAPI...");
+ if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
+ getLogger().info("PlaceholderAPI found...");
+ getLogger().info("Registering PlaceholderAPI expansion...");
+ new PAPIExpansion().register();
+ }
+
+ long end = System.currentTimeMillis();
+ getLogger().info("Finished loading plugin ("+(end-start)+"ms)");
+ loaded = true;
+
+ }
+
+ public void onDisable() {
+
+ version = 0;
+
+ if(board != null) {
+ board.getPlayers().forEach(player -> {
+ board.removeBoard(player);
+ PlayerLoader.unloadPlayer(player);
+ exitPosition.teleport(player);
+ });
+ board.cleanup();
+ }
+
+ if(disguiser != null) {
+ disguiser.cleanUp();
+ }
+
+ Bukkit.getServer().getMessenger().unregisterOutgoingPluginChannel(this);
+ }
+
+ private void onTick() {
+ if(game.getStatus() == Status.ENDED) game = new Game(game.getCurrentMap(), board);
+ game.onTick();
+ disguiser.check();
+ }
+
+ private void registerListeners() {
+ getServer().getPluginManager().registerEvents(new BlockedCommandHandler(), this);
+ getServer().getPluginManager().registerEvents(new ChatHandler(), this);
+ getServer().getPluginManager().registerEvents(new DamageHandler(), this);
+ getServer().getPluginManager().registerEvents(new DisguiseHandler(), this);
+ getServer().getPluginManager().registerEvents(new InteractHandler(), this);
+ getServer().getPluginManager().registerEvents(new InventoryHandler(), this);
+ getServer().getPluginManager().registerEvents(new JoinLeaveHandler(), this);
+ getServer().getPluginManager().registerEvents(new MovementHandler(), this);
+ getServer().getPluginManager().registerEvents(new PlayerHandler(), this);
+ getServer().getPluginManager().registerEvents(new RespawnHandler(), this);
+ getServer().getPluginManager().registerEvents(new WorldInteractHandler(), this);
+ }
+
+ private void updateVersion(){
+ Matcher matcher = Pattern.compile("MC: \\d\\.(\\d+).(\\d+)").matcher(Bukkit.getVersion());
+ if (matcher.find()) {
+ version = Integer.parseInt(matcher.group(1));
+ sub_version = Integer.parseInt(matcher.group(2));
+
+ getLogger().info("Identified server version: " + version);
+ getLogger().info("Identified server sub version: " + sub_version);
+
+ return;
+ }
+
+ matcher = Pattern.compile("MC: \\d\\.(\\d+)").matcher(Bukkit.getVersion());
+ if (matcher.find()) {
+ version = Integer.parseInt(matcher.group(1));
+ sub_version = 0;
+
+ getLogger().info("Identified server version: " + version);
+
+ return;
+ }
+
+ throw new IllegalArgumentException("Failed to parse server version from: " + Bukkit.getVersion());
+ }
+
+ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(errorPrefix + message("COMMAND_PLAYER_ONLY"));
+ return true;
+ }
+ commandGroup.handleCommand((Player)sender, args);
+ return true;
+ }
+
+ public java.util.List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(errorPrefix + message("COMMAND_PLAYER_ONLY"));
+ return new ArrayList<>();
+ }
+ return commandGroup.handleTabComplete((Player)sender, args);
+ }
+
+ public static Main getInstance() {
+ return instance;
+ }
+
+ public File getWorldContainer() {
+ return this.getServer().getWorldContainer();
+ }
+
+ public Database getDatabase() {
+ return database;
+ }
+
+ public Board getBoard(){
+ return board;
+ }
+
+ public Game getGame(){
+ return game;
+ }
+
+ public Disguiser getDisguiser() { return disguiser; }
+
+ public EntityHider getEntityHider() { return entityHider; }
+
+ public CommandGroup getCommandGroup() { return commandGroup; }
+
+ public boolean supports(int v){
+ return version >= v;
+ }
+
+ public boolean supports(int v, int s){
+ return (version == v) ? sub_version >= s : version >= v;
+ }
+
+ public java.util.List<String> getWorlds() {
+ java.util.List<String> worlds = new ArrayList<>();
+ File[] containers = getWorldContainer().listFiles();
+ if(containers != null) {
+ Arrays.stream(containers).forEach(file -> {
+ if (!file.isDirectory()) return;
+ String[] files = file.list();
+ if (files == null) return;
+ if (!Arrays.asList(files).contains("session.lock") && !Arrays.asList(files).contains("level.dat")) return;
+ worlds.add(file.getName());
+ });
+ }
+ return worlds;
+ }
+
+ public boolean isLoaded() {
+ return loaded;
+ }
+
+ public void scheduleTask(Runnable task) {
+ if(!isEnabled()) return;
+ Bukkit.getServer().getScheduler().runTask(this, task);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Confirm.java b/src/main/java/dev/tylerm/khs/command/Confirm.java
new file mode 100644
index 0000000..9a13b72
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Confirm.java
@@ -0,0 +1,62 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.command.util.ICommand;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.function.Consumer;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Confirm implements ICommand {
+
+ public static final Map<UUID, Confirmation> confirmations = new HashMap<>();
+
+ public void execute(Player sender, String[] args) {
+ Confirmation confirmation = confirmations.get(sender.getUniqueId());
+ confirmations.remove(sender.getUniqueId());
+ if(confirmation == null) {
+ sender.sendMessage(errorPrefix + message("NO_CONFIRMATION"));
+ } else {
+ long now = System.currentTimeMillis();
+ float secs = (now - confirmation.start) / 1000F;
+ if(secs > 10) {
+ sender.sendMessage(errorPrefix + message("CONFIRMATION_TIMED_OUT"));
+ return;
+ }
+ confirmation.callback.accept(confirmation.data);
+ }
+ }
+
+ public String getLabel() {
+ return "confirm";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "Confirm another command if required";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+ public static class Confirmation {
+ public final Consumer<String> callback;
+ public final String data;
+ public final long start;
+
+ public Confirmation(String data, Consumer<String> callback) {
+ this.callback = callback;
+ this.data = data;
+ this.start = System.currentTimeMillis();
+ }
+
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Help.java b/src/main/java/dev/tylerm/khs/command/Help.java
new file mode 100644
index 0000000..5dfd7e9
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Help.java
@@ -0,0 +1,87 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.util.Pair;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Help implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ final int pageSize = 4;
+ List<Pair<String, ICommand>> commands = Main.getInstance().getCommandGroup().getCommands();
+ int pages = (commands.size() - 1) / pageSize + 1;
+ int page;
+ try {
+ if(args.length < 1) {
+ page = 1;
+ } else {
+ page = Integer.parseInt(args[0]);
+ if (page < 1) {
+ throw new IllegalArgumentException("Inavlid Input");
+ }
+ }
+ } catch (Exception e) {
+ sender.sendMessage(errorPrefix + message("WORLDBORDER_INVALID_INPUT").addAmount(args[0]));
+ return;
+ }
+ String spacer = ChatColor.GRAY + "?" + ChatColor.WHITE;
+ StringBuilder message = new StringBuilder();
+ message.append(String.format("%s================ %sHelp: Page (%s/%s) %s================",
+ ChatColor.AQUA, ChatColor.WHITE, page, pages, ChatColor.AQUA));
+ int lines = 0;
+ for(Pair<String, ICommand> pair : commands.stream().skip((long) (page - 1) * pageSize).limit(pageSize).collect(Collectors.toList())) {
+ ICommand command = pair.getRight();
+ String label = pair.getLeft();
+ String start = label.substring(0, label.indexOf(" "));
+ String invoke = label.substring(label.indexOf(" ")+1);
+ message.append(String.format("\n%s %s/%s %s%s %s%s\n%s %s%s%s",
+ spacer,
+ ChatColor.AQUA,
+ start,
+ ChatColor.WHITE,
+ invoke,
+ ChatColor.BLUE,
+ command.getUsage(),
+ spacer,
+ ChatColor.GRAY,
+ ChatColor.ITALIC,
+ command.getDescription()
+ ));
+ lines += 2;
+ }
+ if(lines / 2 < pageSize) {
+ for(int i = 0; i < pageSize * 2 - lines; i++) {
+ message.append("\n").append(spacer);
+ }
+ }
+ message.append("\n").append(ChatColor.AQUA).append("===============================================");
+ sender.sendMessage(message.toString());
+ }
+
+ public String getLabel() {
+ return "help";
+ }
+
+ public String getUsage() {
+ return "<*page>";
+ }
+
+ public String getDescription() {
+ return "Get the commands for the plugin";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return Collections.singletonList(parameter);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Join.java b/src/main/java/dev/tylerm/khs/command/Join.java
new file mode 100644
index 0000000..d8ba212
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Join.java
@@ -0,0 +1,49 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Join implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ sender.sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ Player player = Bukkit.getServer().getPlayer(sender.getName());
+ if (player == null) {
+ sender.sendMessage(errorPrefix + message("COMMAND_ERROR"));
+ return;
+ }
+ if (Main.getInstance().getBoard().contains(player)) {
+ sender.sendMessage(errorPrefix + message("GAME_INGAME"));
+ return;
+ }
+ Main.getInstance().getGame().join(player);
+ }
+
+ public String getLabel() {
+ return "join";
+ }
+
+ public String getUsage() {
+ return "<*map>";
+ }
+
+ public String getDescription() {
+ return "Joins the lobby if game is set to manual join/leave";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Leave.java b/src/main/java/dev/tylerm/khs/command/Leave.java
new file mode 100644
index 0000000..dc485b6
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Leave.java
@@ -0,0 +1,49 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Leave implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ sender.sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ Player player = Bukkit.getServer().getPlayer(sender.getName());
+ if (player == null) {
+ sender.sendMessage(errorPrefix + message("COMMAND_ERROR"));
+ return;
+ }
+ if (!Main.getInstance().getBoard().contains(player)) {
+ sender.sendMessage(errorPrefix + message("GAME_NOT_INGAME"));
+ return;
+ }
+ Main.getInstance().getGame().leave(player);
+ }
+
+ public String getLabel() {
+ return "leave";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "Leaves the lobby if game is set to manual join/leave";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/Reload.java b/src/main/java/dev/tylerm/khs/command/Reload.java
new file mode 100644
index 0000000..098af6f
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Reload.java
@@ -0,0 +1,55 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.*;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Config.messagePrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Reload implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(errorPrefix + message("GAME_INPROGRESS"));
+ return;
+ }
+
+ try {
+ Config.loadConfig();
+ Maps.loadMaps();
+ Localization.loadLocalization();
+ Items.loadItems();
+ Leaderboard.loadLeaderboard();
+ } catch (Exception e) {
+ sender.sendMessage(errorPrefix + message("CONFIG_ERROR"));
+ return;
+ }
+
+ sender.sendMessage(messagePrefix + message("CONFIG_RELOAD"));
+ }
+
+ public String getLabel() {
+ return "reload";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "Reloads the config";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Send.java b/src/main/java/dev/tylerm/khs/command/Send.java
new file mode 100644
index 0000000..6c8d449
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Send.java
@@ -0,0 +1,68 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Send implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+
+ if(map.isNotSetup()){
+ sender.sendMessage(Config.errorPrefix + Localization.message("MAP_NOT_SETUP"));
+ return;
+ }
+
+ if (!Main.getInstance().getBoard().contains(sender)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_NOT_INGAME"));
+ return;
+ }
+
+ Main.getInstance().getGame().setCurrentMap(map);
+ Main.getInstance().getBoard().reloadLobbyBoards();
+ for(Player player : Main.getInstance().getBoard().getPlayers()) {
+ map.getLobby().teleport(player);
+ }
+
+ }
+
+ public String getLabel() {
+ return "send";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Set the current lobby to another map";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().filter(map -> !map.isNotSetup()).map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/SetExitLocation.java b/src/main/java/dev/tylerm/khs/command/SetExitLocation.java
new file mode 100644
index 0000000..9849f81
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/SetExitLocation.java
@@ -0,0 +1,42 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.command.location.LocationUtils;
+import dev.tylerm.khs.command.location.Locations;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class SetExitLocation implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ LocationUtils.setLocation(sender, Locations.EXIT, null, map -> {
+ Config.addToConfig("exit.x", sender.getLocation().getBlockX());
+ Config.addToConfig("exit.y", sender.getLocation().getBlockY());
+ Config.addToConfig("exit.z", sender.getLocation().getBlockZ());
+ Config.addToConfig("exit.world", sender.getLocation().getWorld().getName());
+ Config.exitPosition = Location.from(sender);
+ Config.saveConfig();
+ });
+ }
+
+ public String getLabel() {
+ return "setexit";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "Sets the plugins exit location";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Start.java b/src/main/java/dev/tylerm/khs/command/Start.java
new file mode 100644
index 0000000..0a75855
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Start.java
@@ -0,0 +1,77 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Start implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ sender.sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(errorPrefix + message("GAME_INPROGRESS"));
+ return;
+ }
+ if (!Main.getInstance().getBoard().contains(sender)) {
+ sender.sendMessage(errorPrefix + message("GAME_NOT_INGAME"));
+ return;
+ }
+ if (Main.getInstance().getBoard().size() < minPlayers) {
+ sender.sendMessage(errorPrefix + message("START_MIN_PLAYERS").addAmount(minPlayers));
+ return;
+ }
+
+ if (args.length < 1) {
+ Main.getInstance().getGame().start();
+ return;
+ };
+
+ List<Player> initialSeekers = new ArrayList<>();
+ for (int i = 0; i < args.length; i++) {
+ Player seeker = Bukkit.getPlayer(args[i]);
+ if (seeker == null || !Main.getInstance().getBoard().contains(seeker) || initialSeekers.contains(seeker)) {
+ sender.sendMessage(errorPrefix + message("START_INVALID_NAME").addPlayer(args[i]));
+ return;
+ }
+ initialSeekers.add(seeker);
+ }
+
+ int minHiders = minPlayers - startingSeekerCount;
+ if (Main.getInstance().getBoard().size() - initialSeekers.size() < minHiders) {
+ sender.sendMessage(errorPrefix + message("START_MIN_PLAYERS").addAmount(minPlayers));
+ return;
+ }
+
+ Main.getInstance().getGame().start(initialSeekers);
+ }
+
+ public String getLabel() {
+ return "start";
+ }
+
+ public String getUsage() {
+ return "<*seekers...>";
+ }
+
+ public String getDescription() {
+ return "Starts the game either with a random set of seekers or a chosen list";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return Main.getInstance().getBoard().getPlayers().stream().map(Player::getName).collect(Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Stop.java b/src/main/java/dev/tylerm/khs/command/Stop.java
new file mode 100644
index 0000000..557e147
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Stop.java
@@ -0,0 +1,46 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.abortPrefix;
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Stop implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ sender.sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() == Status.STARTING || Main.getInstance().getGame().getStatus() == Status.PLAYING) {
+ Main.getInstance().getGame().broadcastMessage(abortPrefix + message("STOP"));
+ Main.getInstance().getGame().end();
+ } else {
+ sender.sendMessage(errorPrefix + message("GAME_NOT_INPROGRESS"));
+ }
+ }
+
+ public String getLabel() {
+ return "stop";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "Stops the game";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Top.java b/src/main/java/dev/tylerm/khs/command/Top.java
new file mode 100644
index 0000000..27a438c
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Top.java
@@ -0,0 +1,72 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.database.util.PlayerInfo;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Top implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ int page;
+ if (args.length == 0) page = 1;
+ else try{
+ page = Integer.parseInt(args[0]);
+ } catch(Exception e) {
+ sender.sendMessage(errorPrefix + message("WORLDBORDER_INVALID_INPUT").addAmount(args[0]));
+ return;
+ }
+ if (page < 1) {
+ sender.sendMessage(errorPrefix + message("WORLDBORDER_INVALID_INPUT").addAmount(page));
+ return;
+ }
+ StringBuilder message = new StringBuilder(String.format(
+ "%s------- %sLEADERBOARD %s(Page %s) %s-------\n",
+ ChatColor.WHITE, ChatColor.BOLD, ChatColor.GRAY, page, ChatColor.WHITE));
+ List<PlayerInfo> infos = Main.getInstance().getDatabase().getGameData().getInfoPage(page);
+ int i = 1 + (page-1)*10;
+ if (infos == null) {
+ sender.sendMessage(errorPrefix + message("NO_GAME_INFO"));
+ return;
+ }
+ for(PlayerInfo info : infos) {
+ String name = Main.getInstance().getDatabase().getNameData().getName(info.getUniqueId());
+ ChatColor color;
+ switch (i) {
+ case 1: color = ChatColor.YELLOW; break;
+ case 2: color = ChatColor.GRAY; break;
+ case 3: color = ChatColor.GOLD; break;
+ default: color = ChatColor.WHITE; break;
+ }
+ message.append(String.format("%s%s. %s%s %s%s\n",
+ color, i, ChatColor.RED, info.getSeekerWins() +info.getHiderWins(), ChatColor.WHITE, name));
+ i++;
+ }
+ sender.sendMessage(message.toString());
+ }
+
+ public String getLabel() {
+ return "top";
+ }
+
+ public String getUsage() {
+ return "<*page>";
+ }
+
+ public String getDescription() {
+ return "Gets the top players in the server.";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return Collections.singletonList(parameter);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/Wins.java b/src/main/java/dev/tylerm/khs/command/Wins.java
new file mode 100644
index 0000000..2a7f046
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/Wins.java
@@ -0,0 +1,67 @@
+package dev.tylerm.khs.command;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.database.util.PlayerInfo;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class Wins implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ Main.getInstance().getServer().getScheduler().runTaskAsynchronously(Main.getInstance(), () -> {
+
+ UUID uuid;
+ String name;
+ if (args.length == 0) {
+ uuid = sender.getUniqueId();
+ name = sender.getName();
+ }
+ else {
+ name = args[0];
+ uuid = Main.getInstance().getDatabase().getNameData().getUUID(args[0]);
+ }
+ if(uuid == null){
+ sender.sendMessage(Config.errorPrefix + Localization.message("START_INVALID_NAME").addPlayer(args[0]));
+ return;
+ }
+ PlayerInfo info = Main.getInstance().getDatabase().getGameData().getInfo(uuid);
+ if (info == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("NO_GAME_INFO"));
+ return;
+ }
+ String message = ChatColor.WHITE + "" + ChatColor.BOLD + "==============================\n";
+ message = message + Localization.message("INFORMATION_FOR").addPlayer(name) + "\n";
+ message = message + "==============================\n";
+ message = message + String.format("%sTOTAL WINS: %s%s\n%sHIDER WINS: %s%s\n%sSEEKER WINS: %s%s\n%sGAMES PLAYED: %s",
+ ChatColor.YELLOW, ChatColor.WHITE, info.getSeekerWins() +info.getHiderWins(), ChatColor.GOLD, ChatColor.WHITE, info.getHiderWins(),
+ ChatColor.RED, ChatColor.WHITE, info.getSeekerWins(), ChatColor.WHITE, info.getSeekerGames() +info.getHiderGames());
+ message = message + ChatColor.WHITE + "" + ChatColor.BOLD + "\n==============================";
+ sender.sendMessage(message);
+
+ });
+ }
+
+ public String getLabel() {
+ return "wins";
+ }
+
+ public String getUsage() {
+ return "<*player>";
+ }
+
+ public String getDescription() {
+ return "Get the win information for yourself or another player.";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return Collections.singletonList(parameter);
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/command/location/LocationUtils.java b/src/main/java/dev/tylerm/khs/command/location/LocationUtils.java
new file mode 100644
index 0000000..8c8e3f7
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/location/LocationUtils.java
@@ -0,0 +1,52 @@
+package dev.tylerm.khs.command.location;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+
+/**
+ * @author bobby29831
+ */
+public class LocationUtils {
+
+ public static void setLocation(@NotNull Player player, @NotNull Locations place, String mapName, @NotNull Consumer<Map> consumer) {
+
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ player.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+
+ if (player.getLocation().getBlockX() == 0 || player.getLocation().getBlockZ() == 0 || player.getLocation().getBlockY() == 0){
+ player.sendMessage(Config.errorPrefix + Localization.message("NOT_AT_ZERO"));
+ return;
+ }
+
+ Map map = null;
+ if(mapName != null) {
+ map = Maps.getMap(mapName);
+ if (map == null) {
+ player.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ }
+
+ try {
+ consumer.accept(map);
+ } catch (Exception e) {
+ player.sendMessage(Config.errorPrefix + e.getMessage());
+ return;
+ }
+
+ if(map != null)
+ Maps.setMap(mapName, map);
+ player.sendMessage(Config.messagePrefix + Localization.message(place.message()));
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/location/Locations.java b/src/main/java/dev/tylerm/khs/command/location/Locations.java
new file mode 100644
index 0000000..c3d505c
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/location/Locations.java
@@ -0,0 +1,17 @@
+package dev.tylerm.khs.command.location;
+
+/**
+ * @author bobby29831
+ */
+public enum Locations {
+
+ GAME,
+ LOBBY,
+ EXIT,
+ SEEKER;
+
+ public String message() {
+ return this + "_SPAWN";
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/map/Add.java b/src/main/java/dev/tylerm/khs/command/map/Add.java
new file mode 100644
index 0000000..761db0f
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/Add.java
@@ -0,0 +1,53 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Add implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map != null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("MAP_ALREADY_EXISTS"));
+ } else if(!args[0].matches("[a-zA-Z0-9]*") || args[0].length() < 1) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP_NAME"));
+ } else {
+ Maps.setMap(args[0], new Map(args[0]));
+ sender.sendMessage(Config.messagePrefix + Localization.message("MAP_CREATED").addAmount(args[0]));
+ }
+ }
+
+ public String getLabel() {
+ return "add";
+ }
+
+ public String getUsage() {
+ return "<name>";
+ }
+
+ public String getDescription() {
+ return "Add a map to the plugin!";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("name")) {
+ return Collections.singletonList("name");
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/Debug.java b/src/main/java/dev/tylerm/khs/command/map/Debug.java
new file mode 100644
index 0000000..07f4e69
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/Debug.java
@@ -0,0 +1,122 @@
+package dev.tylerm.khs.command.map;
+
+import com.cryptomorin.xseries.XMaterial;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.PlayerLoader;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class Debug implements ICommand {
+
+ private static final Map<Player, Map<Integer, Consumer<Player>>> debugMenuFunctions = new HashMap<>();
+
+ public void execute(Player sender, String[] args) {
+ dev.tylerm.khs.configuration.Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ Inventory debugMenu = createMenu(map, sender);
+ sender.openInventory(debugMenu);
+ }
+
+ private Inventory createMenu(dev.tylerm.khs.configuration.Map map, Player sender){
+ Map<Integer, Consumer<Player>> functions = new HashMap<>();
+ Inventory debugMenu = Main.getInstance().getServer().createInventory(null, 9, "Debug Menu");
+ debugMenu.setItem(0, createOption(functions, 0, XMaterial.LEATHER_CHESTPLATE.parseMaterial(), "&6Become a &lHider", 1, player -> {
+ if(Config.mapSaveEnabled) {
+ if(map.getGameSpawn().getWorld() == null) map.getWorldLoader().loadMap();
+ }
+ Main.getInstance().getBoard().addHider(player);
+ PlayerLoader.loadHider(player, map);
+ if(Main.getInstance().getGame().getStatus() != dev.tylerm.khs.game.util.Status.STARTING)
+ PlayerLoader.resetPlayer(player, Main.getInstance().getBoard());
+ }));
+ debugMenu.setItem(1, createOption(functions, 1, XMaterial.GOLDEN_CHESTPLATE.parseMaterial(), "&cBecome a &lSeeker", 1, player -> {
+ if(Config.mapSaveEnabled) {
+ if(map.getGameSpawn().getWorld() == null) map.getWorldLoader().loadMap();
+ }
+ Main.getInstance().getBoard().addSeeker(player);
+ PlayerLoader.loadSeeker(player, map);
+ if(Main.getInstance().getGame().getStatus() != dev.tylerm.khs.game.util.Status.STARTING)
+ PlayerLoader.resetPlayer(player, Main.getInstance().getBoard());
+ }));
+ debugMenu.setItem(2, createOption(functions, 2, XMaterial.IRON_CHESTPLATE.parseMaterial(), "&8Become a &lSpectator", 1, player -> {
+ if(Config.mapSaveEnabled) {
+ if(map.getGameSpawn().getWorld() == null) map.getWorldLoader().loadMap();
+ }
+ Main.getInstance().getBoard().addSpectator(player);
+ PlayerLoader.loadSpectator(player, map);
+ }));
+ debugMenu.setItem(3, createOption(functions, 3, XMaterial.BARRIER.parseMaterial(), "&cUnload from Game", 1, player -> {
+ Main.getInstance().getBoard().remove(player);
+ PlayerLoader.unloadPlayer(player);
+ Config.exitPosition.teleport(player);
+ }));
+ debugMenu.setItem(4, createOption(functions, 4, XMaterial.BARRIER.parseMaterial(), "&cDie In Game", 2, player -> {
+ if((Main.getInstance().getBoard().isSeeker(player) || Main.getInstance().getBoard().isHider(player)) && Main.getInstance().getGame().getStatus() == Status.PLAYING){
+ player.setHealth(0.1);
+ }
+ }));
+ if(map.isBlockHuntEnabled()) {
+ debugMenu.setItem(7, createOption(functions, 7, XMaterial.GLASS.parseMaterial(), "&dEnable Disguise", 1, player -> {
+ PlayerLoader.openBlockHuntPicker(player, map);
+ }));
+ debugMenu.setItem(8, createOption(functions, 8, XMaterial.PLAYER_HEAD.parseMaterial(), "&dDisable Disguise", 1, player -> Main.getInstance().getDisguiser().reveal(player)));
+ }
+ debugMenuFunctions.put(sender, functions);
+ return debugMenu;
+ }
+
+ private ItemStack createOption(Map<Integer, Consumer<Player>> functions, int slow, Material material, String name, int amount, Consumer<Player> callback){
+ ItemStack temp = new ItemStack(material, amount);
+ ItemMeta meta = temp.getItemMeta();
+ meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name));
+ temp.setItemMeta(meta);
+ functions.put(slow, callback);
+ return temp;
+ }
+
+ public static void handleOption(Player player, int slotId){
+ Main.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> {
+ Consumer<Player> callback = debugMenuFunctions.get(player).get(slotId);
+ if(callback != null) callback.accept(player);
+ }, 0);
+ }
+
+ public String getLabel() {
+ return "debug";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Run debug commands";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(dev.tylerm.khs.configuration.Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/GoTo.java b/src/main/java/dev/tylerm/khs/command/map/GoTo.java
new file mode 100644
index 0000000..5cce5a6
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/GoTo.java
@@ -0,0 +1,62 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GoTo implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("MAP_NOT_SETUP").addAmount(map.getName()));
+ return;
+ }
+ switch (args[1].toLowerCase()) {
+ case "spawn":
+ map.getSpawn().teleport(sender); break;
+ case "lobby":
+ map.getLobby().teleport(sender); break;
+ case "seekerlobby":
+ map.getSeekerLobby().teleport(sender); break;
+ case "exit":
+ Config.exitPosition.teleport(sender); break;
+ default:
+ sender.sendMessage(Config.errorPrefix + Localization.message("COMMAND_INVALID_ARG").addAmount(args[1].toLowerCase()));
+ }
+ }
+
+ public String getLabel() {
+ return "goto";
+ }
+
+ public String getUsage() {
+ return "<map> <spawn>";
+ }
+
+ public String getDescription() {
+ return "Teleport to a map spawn zone";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ } else if(parameter.equals("spawn")) {
+ return Arrays.asList("spawn","lobby","seekerlobby","exit");
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/List.java b/src/main/java/dev/tylerm/khs/command/map/List.java
new file mode 100644
index 0000000..987138d
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/List.java
@@ -0,0 +1,45 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+public class List implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ Collection<Map> maps = Maps.getAllMaps();
+ if(maps.size() < 1) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("NO_MAPS"));
+ return;
+ }
+ StringBuilder response = new StringBuilder(Config.messagePrefix + Localization.message("LIST_MAPS"));
+ for(Map map : maps) {
+ response.append("\n ").append(map.getName()).append(": ").append(map.isNotSetup() ? ChatColor.RED + "NOT SETUP" : ChatColor.GREEN + "SETUP").append(ChatColor.WHITE);
+ }
+ sender.sendMessage(response.toString());
+ }
+
+ public String getLabel() {
+ return "list";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "List all maps in the plugin";
+ }
+
+ public java.util.List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/map/Remove.java b/src/main/java/dev/tylerm/khs/command/map/Remove.java
new file mode 100644
index 0000000..b5231a5
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/Remove.java
@@ -0,0 +1,52 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Remove implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ } else if(!Maps.removeMap(args[0])){
+ sender.sendMessage(Config.errorPrefix + Localization.message("MAP_FAIL_DELETE").addAmount(args[0]));
+ } else {
+ sender.sendMessage(Config.messagePrefix + Localization.message("MAP_DELETED").addAmount(args[0]));
+ }
+ }
+
+ public String getLabel() {
+ return "remove";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Remove a map from the plugin!";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/map/Save.java b/src/main/java/dev/tylerm/khs/command/map/Save.java
new file mode 100644
index 0000000..86871d4
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/Save.java
@@ -0,0 +1,83 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Save implements ICommand {
+
+ public static boolean runningBackup = false;
+
+ public void execute(Player sender, String[] args) {
+ if (!Config.mapSaveEnabled) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("MAPSAVE_DISABLED"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.getSpawn().isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_GAME_SPAWN"));
+ return;
+ }
+ if (map.isBoundsNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_MAP_BOUNDS"));
+ return;
+ }
+ sender.sendMessage(Config.messagePrefix + Localization.message("MAPSAVE_START"));
+ sender.sendMessage(Config.warningPrefix + Localization.message("MAPSAVE_WARNING"));
+ World world = map.getSpawn().load();
+ if (world == null) {
+ sender.sendMessage(Config.warningPrefix + Localization.message("MAPSAVE_FAIL_WORLD"));
+ return;
+ }
+ world.save();
+ BukkitRunnable runnable = new BukkitRunnable() {
+ public void run() {
+ sender.sendMessage(
+ map.getWorldLoader().save()
+ );
+ runningBackup = false;
+ }
+ };
+ runnable.runTaskAsynchronously(Main.getInstance());
+ runningBackup = true;
+ }
+
+ public String getLabel() {
+ return "save";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Saves the map to its own separate playable map";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/Status.java b/src/main/java/dev/tylerm/khs/command/map/Status.java
new file mode 100644
index 0000000..869fc26
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/Status.java
@@ -0,0 +1,79 @@
+package dev.tylerm.khs.command.map;
+
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Status implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+
+ String msg = Localization.message("SETUP").toString();
+ int count = 0;
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.getSpawn().getBlockX() == 0 && map.getSpawn().getBlockY() == 0 && map.getSpawn().getBlockZ() == 0 || !map.getSpawn().exists()) {
+ msg = msg + "\n" + Localization.message("SETUP_GAME");
+ count++;
+ }
+ if (map.getLobby().getBlockX() == 0 && map.getLobby().getBlockY() == 0 && map.getLobby().getBlockZ() == 0 || !map.getLobby().exists()) {
+ msg = msg + "\n" + Localization.message("SETUP_LOBBY");
+ count++;
+ }
+ if (map.getSeekerLobby().getBlockX() == 0 && map.getSeekerLobby().getBlockY() == 0 && map.getSeekerLobby().getBlockZ() == 0 || !map.getSeekerLobby().exists()) {
+ msg = msg + "\n" + Localization.message("SETUP_SEEKER_LOBBY");
+ count++;
+ }
+ if (Config.exitPosition.getBlockX() == 0 && Config.exitPosition.getBlockY() == 0 && Config.exitPosition.getBlockZ() == 0 || !Config.exitPosition.exists()) {
+ msg = msg + "\n" + Localization.message("SETUP_EXIT");
+ count++;
+ }
+ if (map.isBoundsNotSetup()) {
+ msg = msg + "\n" + Localization.message("SETUP_BOUNDS");
+ count++;
+ }
+ if (Config.mapSaveEnabled && !map.getGameSpawn().exists()) {
+ msg = msg + "\n" + Localization.message("SETUP_SAVEMAP");
+ count++;
+ }
+ if (map.isBlockHuntEnabled() && map.getBlockHunt().isEmpty()) {
+ msg = msg + "\n" + Localization.message("SETUP_BLOCKHUNT");
+ count++;
+ }
+ if (count < 1) {
+ sender.sendMessage(Config.messagePrefix + Localization.message("SETUP_COMPLETE"));
+ } else {
+ sender.sendMessage(msg);
+ }
+ }
+
+ public String getLabel() {
+ return "status";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Shows what needs to be setup on a map";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/blockhunt/Enabled.java b/src/main/java/dev/tylerm/khs/command/map/blockhunt/Enabled.java
new file mode 100644
index 0000000..1d49473
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/blockhunt/Enabled.java
@@ -0,0 +1,63 @@
+package dev.tylerm.khs.command.map.blockhunt;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Enabled implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (!Main.getInstance().supports(9)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_UNSUPPORTED"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ boolean bool = Boolean.parseBoolean(args[1]);
+ map.setBlockhunt(bool, map.getBlockHunt());
+ Maps.setMap(map.getName(), map);
+ sender.sendMessage(Config.messagePrefix + Localization.message("BLOCKHUNT_SET_TO")
+ .addAmount(bool ? ChatColor.GREEN + "true" : ChatColor.RED + "false") + ChatColor.WHITE);
+ }
+
+ public String getLabel() {
+ return "enabled";
+ }
+
+ public String getUsage() {
+ return "<map> <bool>";
+ }
+
+ public String getDescription() {
+ return "Sets blockhunt enabled or disabled in a current map";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ if(parameter.equals("bool")) {
+ return Arrays.asList("true", "false");
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Add.java b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Add.java
new file mode 100644
index 0000000..4232bb5
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Add.java
@@ -0,0 +1,75 @@
+package dev.tylerm.khs.command.map.blockhunt.blocks;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Add implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (!Main.getInstance().supports(9)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_UNSUPPORTED"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ Material block;
+ try { block = Material.valueOf(args[1]); }
+ catch (IllegalArgumentException e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("COMMAND_INVALID_ARG").addAmount(args[1]));
+ return;
+ }
+ List<Material> blocks = map.getBlockHunt();
+ if(blocks.contains(block)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_BLOCK_EXISTS").addAmount(args[1]));
+ }
+ blocks.add(block);
+ map.setBlockhunt(map.isBlockHuntEnabled(), blocks);
+ Maps.setMap(map.getName(), map);
+ sender.sendMessage(Config.messagePrefix + Localization.message("BLOCKHUNT_BLOCK_ADDED").addAmount(args[1]));
+ }
+
+ public String getLabel() {
+ return "add";
+ }
+
+ public String getUsage() {
+ return "<map> <block>";
+ }
+
+ public String getDescription() {
+ return "Add a blockhunt block to a map!";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ } else if(parameter.equals("block")) {
+ return Arrays.stream(Material.values())
+ .filter(Material::isBlock)
+ .map(Material::toString)
+ .filter(s -> s.toUpperCase().startsWith(typed.toUpperCase()))
+ .collect(Collectors.toList());
+ }
+ return null;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/List.java b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/List.java
new file mode 100644
index 0000000..9ba90fa
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/List.java
@@ -0,0 +1,58 @@
+package dev.tylerm.khs.command.map.blockhunt.blocks;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.stream.Collectors;
+
+public class List implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (!Main.getInstance().supports(9)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_UNSUPPORTED"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ java.util.List<Material> blocks = map.getBlockHunt();
+ if(blocks.isEmpty()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("NO_BLOCKS"));
+ return;
+ }
+ StringBuilder response = new StringBuilder(Config.messagePrefix + Localization.message("BLOCKHUNT_LIST_BLOCKS"));
+ for(int i = 0; i < blocks.size(); i++) {
+ response.append(String.format("\n%s. %s", i, blocks.get(i).toString()));
+ }
+ sender.sendMessage(response.toString());
+ }
+
+ public String getLabel() {
+ return "list";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "List all blockhunt blocks in a map";
+ }
+
+ public java.util.List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Remove.java b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Remove.java
new file mode 100644
index 0000000..38ab4a3
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/blockhunt/blocks/Remove.java
@@ -0,0 +1,75 @@
+package dev.tylerm.khs.command.map.blockhunt.blocks;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Remove implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (!Main.getInstance().supports(9)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_UNSUPPORTED"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ Material block;
+ try { block = Material.valueOf(args[1]); }
+ catch (IllegalArgumentException e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("COMMAND_INVALID_ARG").addAmount(args[1]));
+ return;
+ }
+ java.util.List<Material> blocks = map.getBlockHunt();
+ if(!blocks.contains(block)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BLOCKHUNT_BLOCK_DOESNT_EXIT").addAmount(args[1]));
+ }
+ blocks.remove(block);
+ map.setBlockhunt(map.isBlockHuntEnabled(), blocks);
+ Maps.setMap(map.getName(), map);
+ sender.sendMessage(Config.messagePrefix + Localization.message("BLOCKHUNT_BLOCK_REMOVED").addAmount(args[1]));
+ }
+
+ public String getLabel() {
+ return "remove";
+ }
+
+ public String getUsage() {
+ return "<map> <block>";
+ }
+
+ public String getDescription() {
+ return "Remove a blockhunt block from a map!";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ } else if(parameter.equals("block")) {
+ return Arrays.stream(Material.values())
+ .filter(Material::isBlock)
+ .map(Material::toString)
+ .filter(s -> s.toUpperCase().startsWith(typed.toUpperCase()))
+ .collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/set/Border.java b/src/main/java/dev/tylerm/khs/command/map/set/Border.java
new file mode 100644
index 0000000..7ef3bf9
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/set/Border.java
@@ -0,0 +1,86 @@
+package dev.tylerm.khs.command.map.set;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Border implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.getSpawn().isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_GAME_SPAWN"));
+ return;
+ }
+
+ int num,delay,change;
+ try { num = Integer.parseInt(args[1]); } catch (Exception e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_INVALID_INPUT").addAmount(args[0]));
+ return;
+ }
+ try { delay = Integer.parseInt(args[2]); } catch (Exception e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_INVALID_INPUT").addAmount(args[1]));
+ return;
+ }
+ try { change = Integer.parseInt(args[3]); } catch (Exception e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_INVALID_INPUT").addAmount(args[2]));
+ return;
+ }
+ if (num < 100) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_MIN_SIZE"));
+ return;
+ }
+ if (change < 1) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_CHANGE_SIZE"));
+ return;
+ }
+ map.setWorldBorderData(
+ sender.getLocation().getBlockX(),
+ sender.getLocation().getBlockZ(),
+ num,
+ delay,
+ change
+ );
+ Maps.setMap(map.getName(), map);
+ sender.sendMessage(Config.messagePrefix + Localization.message("WORLDBORDER_ENABLE").addAmount(num).addAmount(delay).addAmount(change));
+ map.getWorldBorder().resetWorldBorder();
+ }
+
+ public String getLabel() {
+ return "border";
+ }
+
+ public String getUsage() {
+ return "<map> <size> <delay> <move>";
+ }
+
+ public String getDescription() {
+ return "Sets a maps world border information";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return Collections.singletonList(parameter);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/set/Bounds.java b/src/main/java/dev/tylerm/khs/command/map/set/Bounds.java
new file mode 100644
index 0000000..5fde50e
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/set/Bounds.java
@@ -0,0 +1,112 @@
+package dev.tylerm.khs.command.map.set;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Bounds implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.getSpawn().isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_GAME_SPAWN"));
+ return;
+ }
+ if (map.getSeekerLobby().isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_GAME_SEEKER_SPAWN"));
+ return;
+ }
+ if (!sender.getWorld().getName().equals(map.getSpawnName())) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("BOUNDS_WRONG_WORLD"));
+ return;
+ }
+ if (sender.getLocation().getBlockX() == 0 || sender.getLocation().getBlockZ() == 0) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("NOT_AT_ZERO"));
+ return;
+ }
+ boolean first = true;
+ int bxs = map.getBoundsMin().getBlockX();
+ int bzs = map.getBoundsMin().getBlockZ();
+ int bxl = map.getBoundsMax().getBlockX();
+ int bzl = map.getBoundsMax().getBlockZ();
+ if (bxs != 0 && bzs != 0 && bxl != 0 && bzl != 0) {
+ bxs = bzs = bxl = bzl = 0;
+ }
+ if (bxl == 0) {
+ bxl = sender.getLocation().getBlockX();
+ } else if (map.getBoundsMax().getX() < sender.getLocation().getBlockX()) {
+ first = false;
+ bxs = bxl;
+ bxl = sender.getLocation().getBlockX();
+ } else {
+ first = false;
+ bxs = sender.getLocation().getBlockX();
+ }
+ if (bzl == 0) {
+ bzl = sender.getLocation().getBlockZ();
+ } else if (map.getBoundsMax().getZ() < sender.getLocation().getBlockZ()) {
+ first = false;
+ bzs = bzl;
+ bzl = sender.getLocation().getBlockZ();
+ } else {
+ first = false;
+ bzs = sender.getLocation().getBlockZ();
+ }
+ map.setBoundMin(bxs, bzs);
+ map.setBoundMax(bxl, bzl);
+ if(!map.isBoundsNotSetup()) {
+ if(!map.getSpawn().isNotSetup()) {
+ if(map.getSpawn().isNotInBounds(bxs, bxl, bzs, bzl)) {
+ map.setSpawn(Location.getDefault());
+ sender.sendMessage(Config.warningPrefix + Localization.message("WARN_SPAWN_RESET"));
+ }
+ }
+ if(!map.getSeekerLobby().isNotSetup()) {
+ if(map.getSeekerLobby().isNotInBounds(bxs, bxl, bzs, bzl)) {
+ map.setSeekerLobby(Location.getDefault());
+ sender.sendMessage(Config.warningPrefix + Localization.message("WARN_SEEKER_SPAWN_RESET"));
+ }
+ }
+ }
+ Maps.setMap(map.getName(), map);
+ sender.sendMessage(Config.messagePrefix + Localization.message("BOUNDS").addAmount(first ? 1 : 2));
+ }
+
+ public String getLabel() {
+ return "bounds";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Sets the map bounds for the game.";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/set/Lobby.java b/src/main/java/dev/tylerm/khs/command/map/set/Lobby.java
new file mode 100644
index 0000000..a1c8036
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/set/Lobby.java
@@ -0,0 +1,42 @@
+package dev.tylerm.khs.command.map.set;
+
+import dev.tylerm.khs.command.location.LocationUtils;
+import dev.tylerm.khs.command.location.Locations;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Lobby implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ LocationUtils.setLocation(sender, Locations.LOBBY, args[0], map -> {
+ map.setLobby(Location.from(sender));
+ });
+ }
+
+ public String getLabel() {
+ return "lobby";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Sets the maps lobby location";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/set/SeekerLobby.java b/src/main/java/dev/tylerm/khs/command/map/set/SeekerLobby.java
new file mode 100644
index 0000000..7187ac9
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/set/SeekerLobby.java
@@ -0,0 +1,56 @@
+package dev.tylerm.khs.command.map.set;
+
+import dev.tylerm.khs.command.location.LocationUtils;
+import dev.tylerm.khs.command.location.Locations;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.*;
+import dev.tylerm.khs.util.Location;
+import dev.tylerm.khs.configuration.Maps;
+import org.bukkit.entity.Player;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;;
+import java.util.stream.Collectors;
+
+public class SeekerLobby implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ LocationUtils.setLocation(sender, Locations.SEEKER, args[0], map -> {
+ if(map.getSpawn().isNotSetup()) {
+ throw new RuntimeException(Localization.message("GAME_SPAWN_NEEDED").toString());
+ }
+ if(!map.getSpawnName().equals(sender.getLocation().getWorld().getName())) {
+ throw new RuntimeException(Localization.message("SEEKER_LOBBY_INVALID").toString());
+ }
+ map.setSeekerLobby(Location.from(sender));
+ if(!map.isBoundsNotSetup()) {
+ Vector boundsMin = map.getBoundsMin();
+ Vector boundsMax = map.getBoundsMax();
+ if(map.getSeekerLobby().isNotInBounds(boundsMin.getBlockX(), boundsMax.getBlockX(), boundsMin.getBlockZ(), boundsMax.getBlockZ())) {
+ sender.sendMessage(Config.warningPrefix + Localization.message("WARN_MAP_BOUNDS"));
+ }
+ }
+ });
+ }
+
+ public String getLabel() {
+ return "seekerlobby";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Sets the maps seeker lobby location";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/set/Spawn.java b/src/main/java/dev/tylerm/khs/command/map/set/Spawn.java
new file mode 100644
index 0000000..6541fac
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/set/Spawn.java
@@ -0,0 +1,67 @@
+package dev.tylerm.khs.command.map.set;
+
+import dev.tylerm.khs.command.location.LocationUtils;
+import dev.tylerm.khs.command.location.Locations;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.*;
+import dev.tylerm.khs.util.Location;
+import dev.tylerm.khs.configuration.Maps;
+import org.bukkit.entity.Player;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Spawn implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ LocationUtils.setLocation(sender, Locations.GAME, args[0], map -> {
+
+ if (map.isWorldBorderEnabled() &&
+ new Vector(sender.getLocation().getX(), 0, sender.getLocation().getZ()).distance(map.getWorldBorderPos()) > 100) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLDBORDER_POSITION"));
+ throw new RuntimeException("World border not enabled or not in valid position!");
+ }
+
+ map.setSpawn(Location.from(sender));
+
+ if(!map.isBoundsNotSetup()) {
+ Vector boundsMin = map.getBoundsMin();
+ Vector boundsMax = map.getBoundsMax();
+ if(map.getSpawn().isNotInBounds(boundsMin.getBlockX(), boundsMax.getBlockX(), boundsMin.getBlockZ(), boundsMax.getBlockZ())) {
+ sender.sendMessage(Config.warningPrefix + Localization.message("WARN_MAP_BOUNDS"));
+ }
+ }
+
+ if(map.getSeekerLobby().getWorld() != null && !map.getSeekerLobby().getWorld().equals(sender.getLocation().getWorld().getName())) {
+ sender.sendMessage(Config.warningPrefix + Localization.message("SEEKER_LOBBY_SPAWN_RESET"));
+ map.setSeekerLobby(Location.getDefault());
+ }
+
+ if (!sender.getLocation().getWorld().getName().equals(map.getSpawnName()) && Config.mapSaveEnabled) {
+ map.getWorldLoader().unloadMap();
+ }
+ });
+ }
+
+ public String getLabel() {
+ return "spawn";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Sets the maps game spawn location";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/map/unset/Border.java b/src/main/java/dev/tylerm/khs/command/map/unset/Border.java
new file mode 100644
index 0000000..f26b0e1
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/map/unset/Border.java
@@ -0,0 +1,58 @@
+package dev.tylerm.khs.command.map.unset;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class Border implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("GAME_INPROGRESS"));
+ return;
+ }
+ Map map = Maps.getMap(args[0]);
+ if(map == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_MAP"));
+ return;
+ }
+ if (map.getSpawn().isNotSetup()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("ERROR_GAME_SPAWN"));
+ return;
+ }
+ map.setWorldBorderData(0, 0, 0, 0, 0);
+ Config.addToConfig("worldBorder.enabled",false);
+ Config.saveConfig();
+ sender.sendMessage(Config.messagePrefix + Localization.message("WORLDBORDER_DISABLE"));
+ map.getWorldBorder().resetWorldBorder();
+ }
+
+ public String getLabel() {
+ return "border";
+ }
+
+ public String getUsage() {
+ return "<map>";
+ }
+
+ public String getDescription() {
+ return "Removes a maps world border information";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("map")) {
+ return Maps.getAllMaps().stream().map(Map::getName).collect(Collectors.toList());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/util/CommandGroup.java b/src/main/java/dev/tylerm/khs/command/util/CommandGroup.java
new file mode 100644
index 0000000..02777a3
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/util/CommandGroup.java
@@ -0,0 +1,186 @@
+package dev.tylerm.khs.command.util;
+
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.util.Pair;
+import dev.tylerm.khs.util.Tuple;
+import dev.tylerm.khs.command.map.Save;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class CommandGroup {
+
+ private final Map<String, Object> commandRegister;
+ private final String label;
+
+ public CommandGroup(String label, Object... data) {
+ this.label = label;
+ this.commandRegister = new LinkedHashMap<>();
+ for(Object o : data) {
+ registerCommand(o);
+ }
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ private void registerCommand(Object object) {
+ if (object instanceof ICommand) {
+ ICommand command = (ICommand) object;
+ if (!commandRegister.containsKey(command.getLabel())) {
+ commandRegister.put(command.getLabel().toLowerCase(), command);
+ }
+ } else if(object instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) object;
+ if (!commandRegister.containsKey(group.getLabel())) {
+ commandRegister.put(group.getLabel().toLowerCase(), group);
+ }
+ }
+ }
+
+ public void handleCommand(Player player, String[] args) {
+
+ Tuple<ICommand, String, String[]> data = getCommand(args, this.getLabel());
+
+ if (data == null) {
+ player.sendMessage(
+ String.format("%s%sKenshin's Hide and Seek %s(%s1.7.5%s)\n", ChatColor.AQUA, ChatColor.BOLD, ChatColor.GRAY, ChatColor.WHITE, ChatColor.GRAY) +
+ String.format("%sAuthor: %s[KenshinEto]\n", ChatColor.GRAY, ChatColor.WHITE) +
+ String.format("%sHelp Command: %s/hs %shelp", ChatColor.GRAY, ChatColor.AQUA, ChatColor.WHITE)
+ );
+ return;
+ }
+
+ ICommand command = data.getLeft();
+ String permission = data.getCenter();
+ String[] parameters = data.getRight();
+
+ if (Save.runningBackup) {
+ player.sendMessage(Config.errorPrefix + Localization.message("MAPSAVE_INPROGRESS"));
+ return;
+ }
+
+ if (Config.permissionsRequired && !player.hasPermission(permission)) {
+ player.sendMessage(Config.errorPrefix + Localization.message("COMMAND_NOT_ALLOWED"));
+ return;
+ }
+
+ int parameterCount = (int) Arrays.stream(command.getUsage().split(" ")).filter(p -> p.startsWith("<") && !p.startsWith("<*")).count();
+ if(parameters.length < parameterCount) {
+ player.sendMessage(Config.errorPrefix + Localization.message("ARGUMENT_COUNT"));
+ return;
+ }
+
+ try {
+ command.execute(player, parameters);
+ } catch (Exception e) {
+ player.sendMessage(Config.errorPrefix + "An error has occurred.");
+ e.printStackTrace();
+ }
+ }
+
+ @Nullable
+ private Tuple<ICommand, String, String[]> getCommand(String[] args, String permission) {
+ if(args.length < 1) {
+ return null;
+ }
+ String invoke = args[0];
+ if(commandRegister.containsKey(invoke)) {
+ Object o = commandRegister.get(invoke);
+ if (o instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) o;
+ return group.getCommand(
+ Arrays.copyOfRange(args, 1, args.length),
+ permission + "." + group.getLabel()
+ );
+ } else if(o instanceof ICommand) {
+ ICommand command = (ICommand) o;
+ return new Tuple<>(command, permission + "." + command.getLabel(), Arrays.copyOfRange(args, 1, args.length));
+ }
+ }
+ return null;
+ }
+
+ public List<String> handleTabComplete(Player player, String[] args) {
+ return handleTabComplete(player, this.getLabel(), args);
+ }
+
+ private List<String> handleTabComplete(Player player, String permission, String[] args) {
+ String invoke = args[0].toLowerCase();
+ if (args.length == 1) {
+ return new ArrayList<>(commandRegister.keySet())
+ .stream()
+ .filter(handle -> handle.toLowerCase().startsWith(invoke))
+ .filter(handle -> {
+ Object object = commandRegister.get(handle);
+ if (object instanceof ICommand) {
+ ICommand command = (ICommand) object;
+ return !Config.permissionsRequired || player.hasPermission(permission + "." + command.getLabel());
+ } else if (object instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) object;
+ return !Config.permissionsRequired || group.hasPermission(player, permission + "." + group.getLabel());
+ }
+ return false;
+ })
+ .collect(Collectors.toList());
+ } else {
+ if (commandRegister.containsKey(invoke)) {
+ Object object = commandRegister.get(invoke);
+ if (object instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) object;
+ return group.handleTabComplete(player, permission + "." + group.getLabel(), Arrays.copyOfRange(args, 1, args.length));
+ } else if (object instanceof ICommand) {
+ ICommand command = (ICommand) object;
+ String[] usage = command.getUsage().split(" ");
+ if (args.length - 2 < usage.length) {
+ String parameter = usage[args.length - 2];
+ String name = parameter.replace("<", "").replace(">", "");
+ List<String> list = command.autoComplete(name, args[args.length - 1]);
+ if (list != null) {
+ return list;
+ }
+ }
+ }
+ }
+ return new ArrayList<>();
+ }
+ }
+
+ private boolean hasPermission(Player player, String permission) {
+ for(Object object : commandRegister.values()) {
+ if(object instanceof ICommand) {
+ ICommand command = (ICommand) object;
+ if(player.hasPermission(permission + command.getLabel())) return true;
+ } else if(object instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) object;
+ if (group.hasPermission(player, permission + this.label + ".")) return true;
+ }
+ }
+ return false;
+ }
+
+ public List<Pair<String, ICommand>> getCommands() {
+ return getCommands(this.getLabel());
+ }
+
+ private List<Pair<String, ICommand>> getCommands(String prefix) {
+ List<Pair<String, ICommand>> commands = new LinkedList<>();
+ for(Object object : commandRegister.values()) {
+ if(object instanceof ICommand) {
+ ICommand command = (ICommand) object;
+ commands.add(new Pair<>(prefix+" "+command.getLabel(), command));
+ } else if(object instanceof CommandGroup) {
+ CommandGroup group = (CommandGroup) object;
+ commands.addAll(group.getCommands(prefix+" "+group.getLabel()));
+ }
+ }
+ return commands;
+ }
+
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/util/ICommand.java b/src/main/java/dev/tylerm/khs/command/util/ICommand.java
new file mode 100644
index 0000000..615f165
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/util/ICommand.java
@@ -0,0 +1,20 @@
+package dev.tylerm.khs.command.util;
+
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public interface ICommand {
+
+ void execute(Player sender, String[] args);
+
+ String getLabel();
+
+ String getUsage();
+
+ String getDescription();
+
+ List<String> autoComplete(@NotNull String parameter, @NotNull String typed);
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/world/Create.java b/src/main/java/dev/tylerm/khs/command/world/Create.java
new file mode 100644
index 0000000..a3fecdc
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/world/Create.java
@@ -0,0 +1,80 @@
+package dev.tylerm.khs.command.world;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.util.Location;
+import dev.tylerm.khs.command.util.ICommand;
+import org.bukkit.World;
+import org.bukkit.WorldType;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class Create implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ List<String> worlds = Main.getInstance().getWorlds();
+ if(worlds.contains(args[0])) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_EXISTS").addAmount(args[0]));
+ return;
+ }
+ WorldType type;
+ World.Environment environment;
+ switch (args[1]) {
+ case "normal":
+ type = WorldType.NORMAL;
+ environment = World.Environment.NORMAL;
+ break;
+ case "flat":
+ type = WorldType.FLAT;
+ environment = World.Environment.NORMAL;
+ break;
+ case "nether":
+ type = WorldType.NORMAL;
+ environment = World.Environment.NETHER;
+ break;
+ case "end":
+ type = WorldType.NORMAL;
+ environment = World.Environment.THE_END;
+ break;
+ default:
+ sender.sendMessage(Config.errorPrefix + Localization.message("INVALID_WORLD_TYPE").addAmount(args[1]));
+ return;
+ }
+
+ Location temp = new Location(args[0], 0, 0, 0);
+
+ if (temp.load(type, environment) == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_ADDED_FAILED"));
+ } else {
+ sender.sendMessage(Config.messagePrefix + Localization.message("WORLD_ADDED").addAmount(args[0]));
+ }
+
+ }
+
+ public String getLabel() {
+ return "create";
+ }
+
+ public String getUsage() {
+ return "<name> <type>";
+ }
+
+ public String getDescription() {
+ return "Create a new world";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("name")) {
+ return Collections.singletonList("name");
+ }
+ if(parameter.equals("type")) {
+ return Arrays.asList("normal", "flat", "nether", "end");
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/world/Delete.java b/src/main/java/dev/tylerm/khs/command/world/Delete.java
new file mode 100644
index 0000000..aef200e
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/world/Delete.java
@@ -0,0 +1,74 @@
+package dev.tylerm.khs.command.world;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.Confirm;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.world.WorldLoader;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+public class Delete implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ java.util.List<String> worlds = Main.getInstance().getWorlds();
+ if(!worlds.contains(args[0])) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_DOESNT_EXIST").addAmount(args[0]));
+ return;
+ }
+
+ Confirm.Confirmation confirmation = new Confirm.Confirmation(args[0], world -> {
+ java.util.List<String> worlds_now = Main.getInstance().getWorlds();
+ if(!worlds_now.contains(world)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_DOESNT_EXIST").addAmount(world));
+ return;
+ }
+ World bukkit_world = Bukkit.getWorld(world);
+ if(bukkit_world != null && bukkit_world.getPlayers().size() > 0) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_NOT_EMPTY"));
+ return;
+ }
+ String path = Main.getInstance().getWorldContainer().getPath() + File.separator + world;
+ if (!Bukkit.getServer().unloadWorld(world, false)) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_REMOVED_FAILED"));
+ return;
+ }
+ try {
+ WorldLoader.deleteDirectory(new File(path));
+ } catch (Exception e) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_REMOVED_FAILED"));
+ return;
+ }
+ sender.sendMessage(Config.messagePrefix + Localization.message("WORLD_REMOVED").addAmount(world));
+ });
+
+ Confirm.confirmations.put(sender.getUniqueId(), confirmation);
+ sender.sendMessage(Config.messagePrefix + Localization.message("CONFIRMATION"));
+
+ }
+
+ public String getLabel() {
+ return "delete";
+ }
+
+ public String getUsage() {
+ return "<name>";
+ }
+
+ public String getDescription() {
+ return "Delete a world";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("name")) {
+ return Main.getInstance().getWorlds();
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/command/world/List.java b/src/main/java/dev/tylerm/khs/command/world/List.java
new file mode 100644
index 0000000..e907138
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/world/List.java
@@ -0,0 +1,55 @@
+package dev.tylerm.khs.command.world;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.command.util.ICommand;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class List implements ICommand {
+
+ public void execute(Player sender, String[] args) {
+ java.util.List<String> worlds = Main.getInstance().getWorlds();
+ if(worlds.isEmpty()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("NO_WORLDS"));
+ } else {
+ StringBuilder response = new StringBuilder(Config.messagePrefix + Localization.message("LIST_WORLDS"));
+ for (String world : worlds) {
+ String status = ChatColor.GRAY + "NOT LOADED";
+ World bukkit_world = Bukkit.getWorld(world);
+ if(bukkit_world != null) {
+ if(bukkit_world.getEnvironment() == World.Environment.NETHER) {
+ status = ChatColor.RED + "NETHER";
+ } else if(bukkit_world.getEnvironment() == World.Environment.THE_END) {
+ status = ChatColor.YELLOW + "THE END";
+ } else {
+ status = ChatColor.GREEN + bukkit_world.getWorldType().toString();
+ }
+ }
+ response.append("\n ").append(world).append(": ").append(status).append(ChatColor.WHITE);
+ }
+ sender.sendMessage(response.toString());
+ }
+ }
+
+ public String getLabel() {
+ return "list";
+ }
+
+ public String getUsage() {
+ return "";
+ }
+
+ public String getDescription() {
+ return "List all worlds in the server";
+ }
+
+ public java.util.List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/command/world/Tp.java b/src/main/java/dev/tylerm/khs/command/world/Tp.java
new file mode 100644
index 0000000..a2ada0a
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/command/world/Tp.java
@@ -0,0 +1,48 @@
+package dev.tylerm.khs.command.world;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.util.ICommand;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.configuration.Localization;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class Tp implements ICommand {
+ public void execute(Player sender, String[] args) {
+ Location test = new Location(args[0], 0, 0,0);
+ if(!test.exists()) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_DOESNT_EXIT"));
+ return;
+ }
+ World world = test.load();
+ if(world == null) {
+ sender.sendMessage(Config.errorPrefix + Localization.message("WORLD_LOAD_FAILED"));
+ return;
+ }
+ Location loc = new Location(world.getName(), world.getSpawnLocation());
+ loc.teleport(sender);
+ }
+
+ public String getLabel() {
+ return "tp";
+ }
+
+ public String getUsage() {
+ return "<world>";
+ }
+
+ public String getDescription() {
+ return "Teleport to another world";
+ }
+
+ public List<String> autoComplete(@NotNull String parameter, @NotNull String typed) {
+ if(parameter.equals("world")) {
+ return Main.getInstance().getWorlds();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/Config.java b/src/main/java/dev/tylerm/khs/configuration/Config.java
new file mode 100644
index 0000000..36d74d8
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Config.java
@@ -0,0 +1,289 @@
+package dev.tylerm.khs.configuration;
+
+import com.cryptomorin.xseries.XItemStack;
+import com.cryptomorin.xseries.XMaterial;
+import com.cryptomorin.xseries.XSound;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.util.CountdownDisplay;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public class Config {
+
+ private static ConfigManager config;
+
+ public static String
+ messagePrefix,
+ errorPrefix,
+ tauntPrefix,
+ worldBorderPrefix,
+ abortPrefix,
+ gameOverPrefix,
+ warningPrefix,
+ locale,
+ leaveServer,
+ placeholderError,
+ placeholderNoData,
+ databaseType,
+ databaseHost,
+ databasePort,
+ databaseUser,
+ databasePass,
+ databaseName;
+
+ public static boolean
+ nameTagsVisible,
+ permissionsRequired,
+ announceMessagesToNonPlayers,
+ tauntEnabled,
+ tauntCountdown,
+ tauntLast,
+ alwaysGlow,
+ glowEnabled,
+ glowStackable,
+ pvpEnabled,
+ autoJoin,
+ teleportToExit,
+ lobbyCountdownEnabled,
+ seekerPing,
+ bungeeLeave,
+ lobbyItemStartAdmin,
+ leaveOnEnd,
+ mapSaveEnabled,
+ allowNaturalCauses,
+ saveInventory,
+ delayedRespawn,
+ dontRewardQuit,
+ spawnPatch,
+ dropItems,
+ respawnAsSpectator,
+ waitTillNoneLeft,
+ gameOverTitle,
+ regenHealth;
+
+ public static int
+ minPlayers,
+ gameLength,
+ tauntDelay,
+ glowLength,
+ countdown,
+ changeCountdown,
+ lobbyMin,
+ lobbyMax,
+ seekerPingLevel1,
+ seekerPingLevel2,
+ seekerPingLevel3,
+ lobbyItemLeavePosition,
+ lobbyItemStartPosition,
+ flightToggleItemPosition,
+ teleportItemPosition,
+ startingSeekerCount,
+ delayedRespawnDelay,
+ hidingTimer,
+ endGameDelay;
+
+ public static float
+ seekerPingLeadingVolume,
+ seekerPingVolume,
+ seekerPingPitch;
+
+ public static List<String>
+ blockedCommands,
+ blockedInteracts;
+
+ public static ItemStack
+ lobbyLeaveItem,
+ lobbyStartItem,
+ glowPowerupItem,
+ flightToggleItem,
+ teleportItem;
+
+ public static XSound
+ ringingSound,
+ heartbeatSound;
+
+ public static CountdownDisplay
+ countdownDisplay;
+
+ public static Location
+ exitPosition;
+
+ public static void loadConfig() {
+
+ config = ConfigManager.create("config.yml");
+ config.saveConfig();
+
+ announceMessagesToNonPlayers = config.getBoolean("announceMessagesToNonPlayers");
+
+ //Prefix
+ char SYMBOL = '§';
+ String SYMBOL_STRING = String.valueOf(SYMBOL);
+
+ messagePrefix = config.getString("prefix.default").replace("&", SYMBOL_STRING);
+ errorPrefix = config.getString("prefix.error").replace("&", SYMBOL_STRING);
+ tauntPrefix = config.getString("prefix.taunt").replace("&", SYMBOL_STRING);
+ worldBorderPrefix = config.getString("prefix.border").replace("&", SYMBOL_STRING);
+ abortPrefix = config.getString("prefix.abort").replace("&", SYMBOL_STRING);
+ gameOverPrefix = config.getString("prefix.gameover").replace("&", SYMBOL_STRING);
+ warningPrefix = config.getString("prefix.warning").replace("&", SYMBOL_STRING);
+
+ // Locations
+ exitPosition = new Location(
+ config.getString("exit.world"),
+ config.getInt("exit.x"),
+ config.getInt("exit.y"),
+ config.getInt("exit.z")
+ );
+ mapSaveEnabled = config.getBoolean("mapSaveEnabled");
+
+ //Taunt
+ tauntEnabled = config.getBoolean("taunt.enabled");
+ tauntCountdown = config.getBoolean("taunt.showCountdown");
+ tauntDelay = Math.max(60, config.getInt("taunt.delay"));
+ tauntLast = config.getBoolean("taunt.whenLastPerson");
+
+ //Glow
+ alwaysGlow = config.getBoolean("alwaysGlow") && Main.getInstance().supports(9);
+ glowLength = Math.max(1, config.getInt("glow.time"));
+ glowStackable = config.getBoolean("glow.stackable");
+ glowEnabled = config.getBoolean("glow.enabled") && Main.getInstance().supports(9) && !alwaysGlow;
+ if (glowEnabled) {
+ glowPowerupItem = createItemStack("glow");
+ }
+
+ //Lobby
+ startingSeekerCount = Math.max(1, config.getInt("startingSeekerCount"));
+ waitTillNoneLeft = config.getBoolean("waitTillNoneLeft");
+ minPlayers = Math.max(1 + startingSeekerCount + (waitTillNoneLeft ? 0 : 1), config.getInt("minPlayers"));
+ countdown = Math.max(10, config.getInt("lobby.countdown"));
+ changeCountdown = Math.max(minPlayers, config.getInt("lobby.changeCountdown"));
+ lobbyMin = Math.max(minPlayers, config.getInt("lobby.min"));
+ lobbyMax = config.getInt("lobby.max");
+ lobbyCountdownEnabled = config.getBoolean("lobby.enabled");
+
+ //SeekerPing
+ seekerPing = config.getBoolean("seekerPing.enabled");
+ seekerPingLevel1 = config.getInt("seekerPing.distances.level1");
+ seekerPingLevel2 = config.getInt("seekerPing.distances.level2");
+ seekerPingLevel3 = config.getInt("seekerPing.distances.level3");
+ seekerPingLeadingVolume = config.getFloat("seekerPing.sounds.leadingVolume");
+ seekerPingVolume = config.getFloat("seekerPing.sounds.volume");
+ seekerPingPitch = config.getFloat("seekerPing.sounds.pitch");
+ Optional<XSound> heartbeatOptional = XSound.matchXSound(config.getString("seekerPing.sounds.heartbeatNoise"));
+ heartbeatSound = heartbeatOptional.orElse(XSound.BLOCK_NOTE_BLOCK_BASEDRUM);
+ Optional<XSound> ringingOptional = XSound.matchXSound(config.getString("seekerPing.sounds.ringingNoise"));
+ ringingSound = ringingOptional.orElse(XSound.BLOCK_NOTE_BLOCK_PLING);
+
+ //Other
+ nameTagsVisible = config.getBoolean("nametagsVisible");
+ permissionsRequired = config.getBoolean("permissionsRequired");
+ gameLength = config.getInt("gameLength");
+ pvpEnabled = config.getBoolean("pvp");
+ allowNaturalCauses = config.getBoolean("allowNaturalCauses");
+ autoJoin = config.getBoolean("autoJoin");
+ teleportToExit = config.getBoolean("teleportToExit");
+ locale = config.getString("locale", "local");
+ blockedCommands = config.getStringList("blockedCommands");
+ leaveOnEnd = config.getBoolean("leaveOnEnd");
+ placeholderError = config.getString("placeholder.incorrect");
+ placeholderNoData = config.getString("placeholder.noData");
+ saveInventory = config.getBoolean("saveInventory");
+ respawnAsSpectator = config.getBoolean("respawnAsSpectator");
+ dontRewardQuit = config.getBoolean("dontRewardQuit");
+ endGameDelay = Math.max(0,config.getInt("endGameDelay"));
+ gameOverTitle = config.getBoolean("gameOverTitle");
+ hidingTimer = Math.max(10, config.getInt("hidingTimer"));
+
+ try {
+ countdownDisplay = CountdownDisplay.valueOf(config.getString("hideCountdownDisplay"));
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException("hideCountdownDisplay: "+config.getString("hideCountdownDisplay")+", is not a valid configuration option!");
+ }
+ blockedInteracts = new ArrayList<>();
+ List<String> tempInteracts = config.getStringList("blockedInteracts");
+ for(String id : tempInteracts) {
+ Optional<XMaterial> optional_mat = XMaterial.matchXMaterial(id);
+ if (optional_mat.isPresent()) {
+ Material mat = optional_mat.get().parseMaterial();
+ if (mat != null) {
+ blockedInteracts.add(mat.name());
+ }
+ }
+ }
+ bungeeLeave = config.getString("leaveType") == null || config.getString("leaveType").equalsIgnoreCase("proxy");
+ leaveServer = config.getString("leaveServer");
+
+ //Lobby Items
+ if (config.getBoolean("lobbyItems.leave.enabled")) {
+ lobbyLeaveItem = createItemStack("lobbyItems.leave");
+ lobbyItemLeavePosition = config.getInt("lobbyItems.leave.position");
+ }
+ if (config.getBoolean("lobbyItems.start.enabled")) {
+ lobbyStartItem = createItemStack("lobbyItems.start");
+ lobbyItemStartAdmin = config.getBoolean("lobbyItems.start.adminOnly");
+ lobbyItemStartPosition = config.getInt("lobbyItems.start.position");
+ }
+
+ //Spectator Items
+ flightToggleItem = createItemStack("spectatorItems.flight");
+ flightToggleItemPosition = config.getInt("spectatorItems.flight.position");
+
+ teleportItem = createItemStack("spectatorItems.teleport");
+ teleportItemPosition = config.getInt("spectatorItems.teleport.position");
+
+ //Database
+ databaseHost = config.getString("databaseHost");
+ databasePort = config.getString("databasePort");
+ databaseUser = config.getString("databaseUser");
+ databasePass = config.getString("databasePass");
+ databaseName = config.getString("databaseName");
+
+ databaseType = config.getString("databaseType").toUpperCase();
+ if(!databaseType.equals("SQLITE") && !databaseType.equals("MYSQL")){
+ throw new RuntimeException("databaseType: "+databaseType+" is not a valid configuration option!");
+ }
+
+ delayedRespawn = config.getBoolean("delayedRespawn.enabled");
+ delayedRespawnDelay = Math.max(0,config.getInt("delayedRespawn.delay"));
+
+ spawnPatch = config.getBoolean("spawnPatch");
+ dropItems = config.getBoolean("dropItems");
+ regenHealth = config.getBoolean("regenHealth");
+
+ }
+
+ public static void addToConfig(String path, Object value) {
+ config.set(path, value);
+ }
+
+ public static void saveConfig() {
+ config.saveConfig();
+ }
+
+ @Nullable
+ private static ItemStack createItemStack(String path){
+ ConfigurationSection item = new YamlConfiguration().createSection("temp");
+ item.set("name", ChatColor.translateAlternateColorCodes('&',config.getString(path+".name")));
+ item.set("material", config.getString(path+".material"));
+ if (Main.getInstance().supports(14)) {
+ if (config.contains(path+".model-data") && config.getInt(path+".model-data") != 0) {
+ item.set("model-data", config.getInt(path+".model-data"));
+ }
+ }
+ List<String> lore = config.getStringList(path+".lore");
+ if (lore != null && !lore.isEmpty()) item.set("lore", lore);
+ ItemStack temp = null;
+ try{ temp = XItemStack.deserialize(item); } catch(Exception ignored) {}
+ return temp;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/ConfigManager.java b/src/main/java/dev/tylerm/khs/configuration/ConfigManager.java
new file mode 100644
index 0000000..3a81cb8
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/ConfigManager.java
@@ -0,0 +1,331 @@
+package dev.tylerm.khs.configuration;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigManager {
+
+ private final File file;
+ private YamlConfiguration config,defaultConfig;
+ private String defaultFilename;
+
+ public static ConfigManager create(String filename) {
+ return new ConfigManager(filename, filename);
+ }
+
+ public static ConfigManager create(String filename, String defaultFilename) {
+ return new ConfigManager(filename, defaultFilename);
+ }
+
+ private ConfigManager(String filename, String defaultFilename) {
+
+ File dataFolder = Main.getInstance().getDataFolder();
+ File oldDataFolder = new File(Main.getInstance().getDataFolder().getParent() + File.separator + "HideAndSeek");
+
+ this.defaultFilename = defaultFilename;
+ this.file = new File(dataFolder, filename);
+
+ if(oldDataFolder.exists()){
+ if(!dataFolder.exists()){
+ if(!oldDataFolder.renameTo(dataFolder)){
+ throw new RuntimeException("Could not rename folder: " + oldDataFolder.getPath());
+ }
+ } else {
+ throw new RuntimeException("Plugin folders for HideAndSeek & KenshinsHideAndSeek both exists. There can only be one!");
+ }
+
+ }
+
+ if (!dataFolder.exists()) {
+ if (!dataFolder.mkdirs()) {
+ throw new RuntimeException("Failed to make directory: " + file.getPath());
+ }
+ }
+
+ if (!file.exists()) {
+ try{
+ InputStream input = Main.getInstance().getResource(defaultFilename);
+ if (input == null) {
+ throw new RuntimeException("Could not create input stream for "+defaultFilename);
+ }
+ java.nio.file.Files.copy(input, file.toPath());
+ input.close();
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ FileInputStream fileInputStream;
+ try {
+ fileInputStream = new FileInputStream(file);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Could not create input stream for "+file.getPath());
+ }
+ InputStreamReader reader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
+ this.config = new YamlConfiguration();
+ try {
+ this.config.load(reader);
+ } catch(InvalidConfigurationException e) {
+ Main.getInstance().getLogger().severe(e.getMessage());
+ throw new RuntimeException("Invalid configuration in config file: "+file.getPath());
+ } catch(IOException e) {
+ throw new RuntimeException("Could not access file: "+file.getPath());
+ }
+
+ InputStream input = this.getClass().getClassLoader().getResourceAsStream(defaultFilename);
+ if (input == null) {
+ throw new RuntimeException("Could not create input stream for "+defaultFilename);
+ }
+ InputStreamReader default_reader = new InputStreamReader(input, StandardCharsets.UTF_8);
+ this.defaultConfig = new YamlConfiguration();
+ try {
+ this.defaultConfig.load(default_reader);
+ } catch(InvalidConfigurationException e) {
+ Main.getInstance().getLogger().severe(e.getMessage());
+ throw new RuntimeException("Invalid configuration in internal config file: "+defaultFilename);
+ } catch(IOException e) {
+ throw new RuntimeException("Could not access internal file: "+defaultFilename);
+ }
+
+ try{
+ fileInputStream.close();
+ default_reader.close();
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to finalize loading of config files.");
+ }
+ }
+
+ public boolean contains(String path) {
+ return config.contains(path);
+ }
+
+ @SuppressWarnings("unused")
+ public double getDouble(String path) {
+ if (!config.contains(path)) {
+ return defaultConfig.getDouble(path);
+ } else {
+ return config.getDouble(path);
+ }
+ }
+
+ public int getInt(String path) {
+ if (!config.contains(path)) {
+ return defaultConfig.getInt(path);
+ } else {
+ return config.getInt(path);
+ }
+ }
+
+ public int getDefaultInt(String path) {
+ return defaultConfig.getInt(path);
+ }
+
+ public float getFloat(String path) {
+ if (!config.contains(path)) {
+ return (float) defaultConfig.getDouble(path);
+ } else {
+ return (float) config.getDouble(path);
+ }
+ }
+
+ public String getString(String path) {
+ String value = config.getString(path);
+ if (value == null) {
+ return defaultConfig.getString(path);
+ } else {
+ return value;
+ }
+ }
+
+ public String getString(String path, String oldPath) {
+ String value = config.getString(path);
+ if (value == null) {
+ String oldValue = config.getString(oldPath);
+ if (oldValue == null) {
+ return defaultConfig.getString(path);
+ } else {
+ return oldValue;
+ }
+ } else {
+ return value;
+ }
+ }
+
+ public List<String> getStringList(String path) {
+ List<String> value = config.getStringList(path);
+ if (value == null) {
+ return defaultConfig.getStringList(path);
+ } else {
+ return value;
+ }
+ }
+
+ public void reset(String path) {
+ config.set(path, defaultConfig.get(path));
+ }
+
+ public void resetFile(String newDefaultFilename) {
+ this.defaultFilename = newDefaultFilename;
+
+ InputStream input = Main.getInstance().getResource(defaultFilename);
+ if (input == null) {
+ throw new RuntimeException("Could not create input stream for "+defaultFilename);
+ }
+ InputStreamReader reader = new InputStreamReader(input);
+ this.config = YamlConfiguration.loadConfiguration(reader);
+ this.defaultConfig = YamlConfiguration.loadConfiguration(reader);
+
+ }
+
+ public boolean getBoolean(String path) {
+ if (!config.contains(path)) {
+ return defaultConfig.getBoolean(path);
+ } else {
+ return config.getBoolean(path);
+ }
+ }
+
+ public ConfigurationSection getConfigurationSection(String path) {
+ ConfigurationSection section = config.getConfigurationSection(path);
+ if (section == null) {
+ return defaultConfig.getConfigurationSection(path);
+ } else {
+ return section;
+ }
+ }
+
+ public ConfigurationSection getDefaultConfigurationSection(String path) {
+ return defaultConfig.getConfigurationSection(path);
+ }
+
+ public void set(String path, Object value) {
+ config.set(path, value);
+ }
+
+ public void overwriteConfig() {
+ try {
+ this.config.save(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void saveConfig() {
+ try {
+ // open config file
+ InputStream is = Main.getInstance().getResource(defaultFilename);
+ // if failed error
+ if (is == null) {
+ throw new RuntimeException("Could not create input stream for "+defaultFilename);
+ }
+ // manually read in each character to preserve string data
+ StringBuilder textBuilder = new StringBuilder(new String("".getBytes(), StandardCharsets.UTF_8));
+ Reader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
+ int c;
+ while((c = reader.read()) != -1)
+ textBuilder.append((char) c);
+ // store yaml file into a string
+ String yaml = new String(textBuilder.toString().getBytes(), StandardCharsets.UTF_8);
+ // get config values
+ Map<String, Object> data = config.getValues(true);
+ // write each stored config value into the yaml string
+ for(Map.Entry<String, Object> entry: data.entrySet()) {
+ // if type isn't supported, skip
+ if(!isSupported(entry.getValue())) continue;
+ // get index of key in yaml string
+ int index = getIndex(yaml, entry.getKey());
+ // if index not found, skip
+ if (index < 10) continue;
+ // get start and end of the value
+ int start = yaml.indexOf(' ', index) + 1;
+ int end = yaml.indexOf('\n', index);
+ // if end not found, set it to the end of the file
+ if (end == -1) end = yaml.length();
+ // create new replace sting
+ StringBuilder replace = new StringBuilder(new String("".getBytes(), StandardCharsets.UTF_8));
+ // get value
+ Object value = entry.getValue();
+ // if the value is a list,
+ if (value instanceof List) {
+ end = yaml.indexOf(']', start) + 1;
+ List<?> list = (List<?>) entry.getValue();
+ if (list.isEmpty()) {
+ // if list is empty, put an empty list
+ replace.append("[]");
+ } else {
+ // if list has values, populate values into the string
+ // get gap before key
+ int gap = whitespaceBefore(yaml, index);
+ String space = new String(new char[gap]).replace('\0', ' ');
+ replace.append("[\n");
+ for (int i = 0; i < list.size(); i++) {
+ replace.append(space).append(" ").append(convert(list.get(i)));
+ if(i != list.size() -1) replace.append(",\n");
+ }
+ replace.append('\n').append(space).append("]");
+ }
+ // otherwise just put the value directly
+ } else {
+ replace.append(convert(value));
+ }
+ // replace the new value in the yaml string
+ StringBuilder builder = new StringBuilder(yaml);
+ builder.replace(start, end, replace.toString());
+ yaml = builder.toString();
+ }
+
+ // write yaml string to file
+ Writer fileWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8));
+ fileWriter.write(yaml);
+ fileWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private int getIndex(String yaml, String key) {
+ String[] parts = key.split("\\.");
+ int index = 0;
+ for(String part : parts) {
+ if (index == 0) {
+ index = yaml.indexOf("\n" + part + ":", index) + 1;
+ } else {
+ index = yaml.indexOf(" " + part + ":", index) + 1;
+ }
+ if (index == 0) break;
+ }
+ return index;
+ }
+
+ public boolean isSupported(Object o) {
+ return o instanceof Integer ||
+ o instanceof Double ||
+ o instanceof String ||
+ o instanceof Boolean ||
+ o instanceof List;
+ }
+
+ public int whitespaceBefore(String yaml, int index) {
+ int count = 0;
+ for(int i = index - 1; yaml.charAt(i) == ' '; i--) count++;
+ return count;
+ }
+
+ private String convert(Object o) {
+ if(o instanceof String) {
+ return "\"" + o + "\"";
+ } else if (o instanceof Boolean) {
+ return (boolean)o ? "true" : "false";
+ }
+ return o.toString();
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/Items.java b/src/main/java/dev/tylerm/khs/configuration/Items.java
new file mode 100644
index 0000000..af8216d
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Items.java
@@ -0,0 +1,220 @@
+package dev.tylerm.khs.configuration;
+
+import com.cryptomorin.xseries.XItemStack;
+import dev.tylerm.khs.Main;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Items {
+
+ public static List<ItemStack> HIDER_ITEMS, SEEKER_ITEMS;
+ public static ItemStack
+ HIDER_HELM, SEEKER_HELM,
+ HIDER_CHEST, SEEKER_CHEST,
+ HIDER_LEGS, SEEKER_LEGS,
+ HIDER_BOOTS, SEEKER_BOOTS;
+
+ public static List<PotionEffect> HIDER_EFFECTS, SEEKER_EFFECTS;
+
+ public static void loadItems() {
+
+ ConfigManager manager = ConfigManager.create("items.yml");
+
+ SEEKER_ITEMS = new ArrayList<>();
+ SEEKER_HELM = null;
+ SEEKER_CHEST = null;
+ SEEKER_LEGS = null;
+ SEEKER_BOOTS = null;
+
+ ConfigurationSection SeekerItems = manager.getConfigurationSection("items.seeker");
+
+ for (int i = 0; i < 9; i++) {
+ ConfigurationSection section = SeekerItems.getConfigurationSection(String.valueOf(i));
+ if (section == null) {
+ SEEKER_ITEMS.add(null);
+ continue;
+ }
+ ItemStack item = createItem(section);
+ SEEKER_ITEMS.add(item);
+ }
+
+ ConfigurationSection SeekerHelmet = SeekerItems.getConfigurationSection("helmet");
+ if (SeekerHelmet != null) {
+ ItemStack item = createItem(SeekerHelmet);
+ if (item != null) {
+ SEEKER_HELM = item;
+ }
+ }
+
+ ConfigurationSection SeekerChestplate = SeekerItems.getConfigurationSection("chestplate");
+ if (SeekerChestplate != null) {
+ ItemStack item = createItem(SeekerChestplate);
+ if (item != null) {
+ SEEKER_CHEST = item;
+ }
+ }
+
+ ConfigurationSection SeekerLeggings = SeekerItems.getConfigurationSection("leggings");
+ if (SeekerLeggings != null) {
+ ItemStack item = createItem(SeekerLeggings);
+ if (item != null) {
+ SEEKER_LEGS = item;
+ }
+ }
+
+ ConfigurationSection SeekerBoots = SeekerItems.getConfigurationSection("boots");
+ if (SeekerBoots != null) {
+ ItemStack item = createItem(SeekerBoots);
+ if (item != null) {
+ SEEKER_BOOTS = item;
+ }
+ }
+
+ HIDER_ITEMS = new ArrayList<>();
+ HIDER_HELM = null;
+ HIDER_CHEST = null;
+ HIDER_LEGS = null;
+ HIDER_BOOTS = null;
+
+ ConfigurationSection HiderItems = manager.getConfigurationSection("items.hider");
+
+ for (int i = 0; i < 9; i++) {
+ ConfigurationSection section = HiderItems.getConfigurationSection(String.valueOf(i));
+ if (section == null) {
+ HIDER_ITEMS.add(null);
+ continue;
+ }
+ ItemStack item = createItem(section);
+ HIDER_ITEMS.add(item);
+ }
+
+ ConfigurationSection HiderHelmet = HiderItems.getConfigurationSection("helmet");
+ if (HiderHelmet != null) {
+ ItemStack item = createItem(HiderHelmet);
+ if (item != null) {
+ HIDER_HELM = item;
+ }
+ }
+
+ ConfigurationSection HiderChestplate = HiderItems.getConfigurationSection("chestplate");
+ if (HiderChestplate != null) {
+ ItemStack item = createItem(HiderChestplate);
+ if (item != null) {
+ HIDER_CHEST = item;
+ }
+ }
+
+ ConfigurationSection HiderLeggings = HiderItems.getConfigurationSection("leggings");
+ if (HiderLeggings != null) {
+ ItemStack item = createItem(HiderLeggings);
+ if (item != null) {
+ HIDER_LEGS = item;
+ }
+ }
+
+ ConfigurationSection HiderBoots = HiderItems.getConfigurationSection("boots");
+ if (HiderBoots != null) {
+ ItemStack item = createItem(HiderBoots);
+ if (item != null) {
+ HIDER_BOOTS = item;
+ }
+ }
+
+ SEEKER_EFFECTS = new ArrayList<>();
+ ConfigurationSection SeekerEffects = manager.getConfigurationSection("effects.seeker");
+
+ int i = 1;
+ while (true) {
+ ConfigurationSection section = SeekerEffects.getConfigurationSection(String.valueOf(i));
+ if (section == null) break;
+ PotionEffect effect = getPotionEffect(section);
+ if (effect != null) SEEKER_EFFECTS.add(effect);
+ i++;
+ }
+
+ HIDER_EFFECTS = new ArrayList<>();
+ ConfigurationSection HiderEffects = manager.getConfigurationSection("effects.hider");
+ i = 1;
+ while (true) {
+ ConfigurationSection section = HiderEffects.getConfigurationSection(String.valueOf(i));
+ if (section == null) break;
+ PotionEffect effect = getPotionEffect(section);
+ if (effect != null) HIDER_EFFECTS.add(effect);
+ i++;
+ }
+ }
+
+ private static ItemStack createItem(ConfigurationSection item) {
+ ConfigurationSection config = new YamlConfiguration().createSection("temp");
+ String material = item.getString("material").toUpperCase();
+ boolean splash = false;
+ if (!Main.getInstance().supports(9)) {
+ if (material.contains("POTION")) {
+ config.set("level", 1);
+ }
+ if (material.equalsIgnoreCase("SPLASH_POTION") || material.equalsIgnoreCase("LINGERING_POTION")) {
+ material = "POTION";
+ splash = true;
+ }
+ }
+ config.set("name", item.getString("name"));
+ config.set("material", material);
+ config.set("enchants", item.getConfigurationSection("enchantments"));
+ config.set("unbreakable", item.getBoolean("unbreakable"));
+ if (Main.getInstance().supports(14)) {
+ if (item.contains("model-data")) {
+ config.set("model-data", item.getInt("model-data"));
+ }
+ }
+ if (item.isSet("lore"))
+ config.set("lore", item.getStringList("lore"));
+ if (material.equalsIgnoreCase("POTION") || material.equalsIgnoreCase("SPLASH_POTION") || material.equalsIgnoreCase("LINGERING_POTION"))
+ config.set("base-effect", String.format("%s,%s,%s", item.getString("type"), false, splash));
+ ItemStack stack = XItemStack.deserialize(config);
+ int amt = item.getInt("amount");
+ if (amt < 1) amt = 1;
+ stack.setAmount(amt);
+ if (stack.getData().getItemType() == Material.AIR) return null;
+ return stack;
+ }
+
+ private static PotionEffect getPotionEffect(ConfigurationSection item) {
+ String type = item.getString("type");
+ if (type == null) return null;
+ if (PotionEffectType.getByName(type.toUpperCase()) == null) return null;
+ return new PotionEffect(
+ PotionEffectType.getByName(type.toUpperCase()),
+ item.getInt("duration"),
+ item.getInt("amplifier"),
+ item.getBoolean("ambient"),
+ item.getBoolean("particles")
+ );
+ }
+
+ public static boolean matchItem(ItemStack stack){
+ for(ItemStack check : HIDER_ITEMS)
+ if(equals(stack,check)) return true;
+ for(ItemStack check : SEEKER_ITEMS)
+ if(equals(stack,check)) return true;
+ return false;
+ }
+
+ private static boolean equals(ItemStack a, ItemStack b) {
+ if (a == null || b == null) {
+ return false;
+ } else if (a == b) {
+ return true;
+ } else {
+ return a.getType() == b.getType() && a.hasItemMeta() == b.hasItemMeta() && (!a.hasItemMeta() || Bukkit.getItemFactory().equals(a.getItemMeta(), b.getItemMeta()));
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/Leaderboard.java b/src/main/java/dev/tylerm/khs/configuration/Leaderboard.java
new file mode 100644
index 0000000..93ba855
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Leaderboard.java
@@ -0,0 +1,51 @@
+package dev.tylerm.khs.configuration;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Leaderboard {
+
+ public static String
+ LOBBY_TITLE,
+ GAME_TITLE,
+ COUNTDOWN_WAITING,
+ COUNTDOWN_COUNTING,
+ COUNTDOWN_ADMINSTART,
+ TAUNT_COUNTING,
+ TAUNT_ACTIVE,
+ TAUNT_EXPIRED,
+ GLOW_ACTIVE,
+ GLOW_INACTIVE,
+ BORDER_COUNTING,
+ BORDER_DECREASING;
+
+ public static List<String>
+ LOBBY_CONTENTS,
+ GAME_CONTENTS;
+
+ public static void loadLeaderboard() {
+
+ ConfigManager leaderboard = ConfigManager.create("leaderboard.yml");
+
+ LOBBY_TITLE = leaderboard.getString("lobby.title");
+ GAME_TITLE = leaderboard.getString("game.title");
+ LOBBY_CONTENTS = leaderboard.getStringList("lobby.content");
+ Collections.reverse(LOBBY_CONTENTS);
+ GAME_CONTENTS = leaderboard.getStringList("game.content");
+ Collections.reverse(GAME_CONTENTS);
+ COUNTDOWN_WAITING = leaderboard.getString("countdown.waiting");
+ COUNTDOWN_COUNTING = leaderboard.getString("countdown.counting");
+ COUNTDOWN_ADMINSTART = leaderboard.getString("countdown.adminStart");
+ TAUNT_COUNTING = leaderboard.getString("taunt.counting");
+ TAUNT_ACTIVE = leaderboard.getString("taunt.active");
+ TAUNT_EXPIRED = leaderboard.getString("taunt.expired");
+ GLOW_ACTIVE = leaderboard.getString("glow.active");
+ GLOW_INACTIVE = leaderboard.getString("glow.inactive");
+ BORDER_COUNTING = leaderboard.getString("border.counting");
+ BORDER_DECREASING = leaderboard.getString("border.decreasing");
+
+ leaderboard.saveConfig();
+
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/Localization.java b/src/main/java/dev/tylerm/khs/configuration/Localization.java
new file mode 100644
index 0000000..2ac84e7
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Localization.java
@@ -0,0 +1,80 @@
+package dev.tylerm.khs.configuration;
+
+import net.md_5.bungee.api.ChatColor;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Localization {
+
+ public static final Map<String,LocalizationString> LOCAL = new HashMap<>();
+ public static final Map<String,LocalizationString> DEFAULT_LOCAL = new HashMap<>();
+
+ private static final Map<String,String[][]> CHANGES = new HashMap<String,String[][]>() {{
+ put("en-US", new String[][]{
+ {"WORLDBORDER_DECREASING"},
+ {"START","TAUNTED"},
+ {"GAME_SETUP", "SETUP_GAME", "SETUP_LOBBY", "SETUP_SEEKER_LOBBY", "SETUP_EXIT", "SETUP_SAVEMAP", "SETUP_BOUNDS"},
+ {"GAME_PLAYER_FOUND", "GAME_PLAYER_FOUND_BY"}
+ });
+ put("de-DE", new String[][]{
+ {},
+ {"TAUNTED"},
+ {"GAME_SETUP", "SETUP_GAME", "SETUP_LOBBY", "SETUP_SEEKER_LOBBY", "SETUP_EXIT", "SETUP_SAVEMAP", "SETUP_BOUNDS"},
+ {"GAME_PLAYER_FOUND", "GAME_PLAYER_FOUND_BY"}
+ });
+ }};
+
+ public static void loadLocalization() {
+
+ ConfigManager manager = ConfigManager.create("localization.yml", "lang/localization_"+Config.locale +".yml");
+
+ int PLUGIN_VERSION = manager.getDefaultInt("version");
+ int VERSION = manager.getInt("version");
+ if (VERSION < PLUGIN_VERSION) {
+ for(int i = VERSION; i < PLUGIN_VERSION; i++) {
+ if (i < 1) continue;
+ String[] changeList = CHANGES.get(Config.locale)[i-1];
+ for(String change : changeList)
+ manager.reset("Localization." + change);
+ }
+ manager.reset("version");
+ }
+
+ String SELECTED_LOCAL = manager.getString("type");
+ if (SELECTED_LOCAL == null) {
+ manager.reset("type");
+ } else if (!SELECTED_LOCAL.equals(Config.locale)) {
+ manager.resetFile("lang"+File.separator+"localization_"+Config.locale +".yml");
+ }
+
+ manager.saveConfig();
+
+ for(String key : manager.getConfigurationSection("Localization").getKeys(false)) {
+ LOCAL.put(
+ key,
+ new LocalizationString( ChatColor.translateAlternateColorCodes('&', manager.getString("Localization."+key) ) )
+ );
+ }
+
+ for(String key : manager.getDefaultConfigurationSection("Localization").getKeys(false)) {
+ DEFAULT_LOCAL.put(
+ key,
+ new LocalizationString( ChatColor.translateAlternateColorCodes('&', manager.getString("Localization."+key) ) )
+ );
+ }
+ }
+
+ public static LocalizationString message(String key) {
+ LocalizationString message = LOCAL.get(key);
+ if (message == null) {
+ LocalizationString defaultMessage = DEFAULT_LOCAL.get(key);
+ if(defaultMessage == null) {
+ return new LocalizationString(ChatColor.RED + "" + ChatColor.ITALIC + key + " is not found in localization.yml. This is a plugin issue, please report it.");
+ }
+ return new LocalizationString(defaultMessage.toString());
+ }
+ return new LocalizationString(message.toString());
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/LocalizationString.java b/src/main/java/dev/tylerm/khs/configuration/LocalizationString.java
new file mode 100644
index 0000000..5dc3724
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/LocalizationString.java
@@ -0,0 +1,37 @@
+package dev.tylerm.khs.configuration;
+
+import org.bukkit.entity.Entity;
+
+public class LocalizationString {
+
+ String message;
+
+ public LocalizationString(String message) {
+ this.message = message;
+ }
+
+ public LocalizationString addPlayer(Entity player) {
+ this.message = message.replaceFirst("\\{PLAYER}", player.getName());
+ return this;
+ }
+
+ public LocalizationString addPlayer(String player) {
+ this.message = message.replaceFirst("\\{PLAYER}", player);
+ return this;
+ }
+
+ public LocalizationString addAmount(Integer value) {
+ this.message = message.replaceFirst("\\{AMOUNT}", value.toString());
+ return this;
+ }
+
+ public LocalizationString addAmount(String value) {
+ this.message = message.replaceFirst("\\{AMOUNT}", value);
+ return this;
+ }
+
+ public String toString() {
+ return message;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/configuration/Map.java b/src/main/java/dev/tylerm/khs/configuration/Map.java
new file mode 100644
index 0000000..7d3ef9f
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Map.java
@@ -0,0 +1,241 @@
+package dev.tylerm.khs.configuration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.events.Border;
+import dev.tylerm.khs.util.Location;
+import dev.tylerm.khs.world.WorldLoader;
+import org.bukkit.*;
+import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+
+import static dev.tylerm.khs.configuration.Config.*;
+
+public class Map {
+
+ private final String name;
+
+ private dev.tylerm.khs.util.Location
+ spawnPosition = dev.tylerm.khs.util.Location.getDefault(),
+ lobbyPosition = dev.tylerm.khs.util.Location.getDefault(),
+ seekerLobbyPosition = dev.tylerm.khs.util.Location.getDefault();
+
+ private int
+ xBoundMin = 0,
+ zBoundMin = 0,
+ xBoundMax = 0,
+ zBoundMax = 0,
+ xWorldBorder = 0,
+ zWorldBorder = 0,
+ worldBorderSize = 0,
+ worldBorderDelay = 0,
+ worldBorderChange = 0;
+
+ private boolean
+ blockhunt = false;
+
+ private List<Material>
+ blockhuntBlocks = new ArrayList<>();
+
+ private final Border
+ worldBorder;
+
+ private final WorldLoader
+ worldLoader;
+
+ public Map(String name) {
+ this.name = name;
+ this.worldBorder = new Border(this);
+ this.worldLoader = new WorldLoader(this);
+ }
+
+ public void setSpawn(dev.tylerm.khs.util.Location pos) {
+ this.spawnPosition = pos;
+ }
+
+ public void setLobby(dev.tylerm.khs.util.Location pos) {
+ this.lobbyPosition = pos;
+ }
+
+ public void setSeekerLobby(dev.tylerm.khs.util.Location pos) {
+ this.seekerLobbyPosition = pos;
+ }
+
+ public void setWorldBorderData(int x, int z, int size, int delay, int move) {
+ if(size < 1) {
+ this.worldBorderSize = 0;
+ this.worldBorderDelay = 0;
+ this.worldBorderChange = 0;
+ this.xWorldBorder = 0;
+ this.zWorldBorder = 0;
+ } else {
+ this.worldBorderSize = size;
+ this.worldBorderDelay = delay;
+ this.worldBorderChange = move;
+ this.xWorldBorder = x;
+ this.zWorldBorder = z;
+ }
+ this.worldBorder.resetWorldBorder();
+ }
+
+ public void setBlockhunt(boolean enabled, List<Material> blocks) {
+ if (Main.getInstance().supports(9)) {
+ this.blockhunt = enabled;
+ } else {
+ this.blockhunt = false;
+ }
+ this.blockhuntBlocks = blocks;
+ }
+
+ public void setBoundMin(int x, int z) {
+ this.xBoundMin = x;
+ this.zBoundMin = z;
+ }
+
+ public void setBoundMax(int x, int z) {
+ this.xBoundMax = x;
+ this.zBoundMax = z;
+ }
+
+ @NotNull
+ public dev.tylerm.khs.util.Location getGameSpawn() {
+ if(mapSaveEnabled) {
+ return spawnPosition.changeWorld("hs_"+name);
+ } else {
+ return spawnPosition;
+ }
+ }
+
+ @NotNull
+ public String getGameSpawnName() {
+ if(mapSaveEnabled)
+ return getGameSpawn().getWorld();
+ else
+ return getSpawn().getWorld();
+ }
+
+ @NotNull
+ public dev.tylerm.khs.util.Location getSpawn() {
+ return spawnPosition;
+ }
+
+ @NotNull
+ public String getSpawnName() {
+ return getSpawn().getWorld();
+ }
+
+ @NotNull
+ public dev.tylerm.khs.util.Location getLobby() {
+ return lobbyPosition;
+ }
+
+ @NotNull
+ public String getLobbyName() {
+ return getLobby().getWorld();
+ }
+
+ @NotNull
+ public dev.tylerm.khs.util.Location getSeekerLobby() {
+ return seekerLobbyPosition;
+ }
+
+ @NotNull
+ public String getSeekerLobbyName() {
+ return getSeekerLobby().getWorld();
+ }
+
+ @NotNull
+ public Location getGameSeekerLobby() {
+ if(mapSaveEnabled) {
+ return seekerLobbyPosition.changeWorld("hs_"+name);
+ } else {
+ return seekerLobbyPosition;
+ }
+ }
+
+ public boolean isWorldBorderEnabled() {
+ return worldBorderSize > 0;
+ }
+
+ @NotNull
+ public Vector getWorldBorderPos() {
+ return new Vector(
+ xWorldBorder,
+ 0,
+ zWorldBorder
+ );
+ }
+
+ @NotNull
+ public Vector getWorldBorderData() {
+ return new Vector(
+ worldBorderSize,
+ worldBorderDelay,
+ worldBorderChange
+ );
+ }
+
+ @NotNull
+ public Border getWorldBorder() {
+ return worldBorder;
+ }
+
+ public boolean isBlockHuntEnabled() {
+ return blockhunt;
+ }
+
+ @NotNull
+ public List<Material> getBlockHunt() {
+ return blockhuntBlocks;
+ }
+
+ @NotNull
+ public Vector getBoundsMin() {
+ return new Vector(
+ xBoundMin,
+ 0,
+ zBoundMin
+ );
+ }
+
+ @NotNull
+ public Vector getBoundsMax() {
+ return new Vector(
+ xBoundMax,
+ 0,
+ zBoundMax
+ );
+ }
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @NotNull
+ public WorldLoader getWorldLoader() {
+ return worldLoader;
+ }
+
+ public boolean isNotSetup() {
+ if (spawnPosition.getBlockX() == 0 && spawnPosition.getBlockY() == 0 && spawnPosition.getBlockZ() == 0 || !spawnPosition.exists()) return true;
+ if (lobbyPosition.getBlockX() == 0 && lobbyPosition.getBlockY() == 0 && lobbyPosition.getBlockZ() == 0 || !lobbyPosition.exists()) return true;
+ if (exitPosition == null || exitPosition.getBlockX() == 0 && exitPosition.getBlockY() == 0 && exitPosition.getBlockZ() == 0 || !exitPosition.exists()) return true;
+ if (seekerLobbyPosition.getBlockX() == 0 && seekerLobbyPosition.getBlockY() == 0 && seekerLobbyPosition.getBlockZ() == 0 || !seekerLobbyPosition.exists()) return true;
+ if (mapSaveEnabled && !getGameSpawn().exists()) return true;
+ if (blockhunt && blockhuntBlocks.isEmpty()) return true;
+ if(isWorldBorderEnabled() &&
+ new Vector(spawnPosition.getX(), 0, spawnPosition.getZ()).distance(new Vector(xWorldBorder, 0, zWorldBorder)) > 100) return true;
+ return isBoundsNotSetup();
+ }
+
+ public boolean isBoundsNotSetup() {
+ if (xBoundMin == 0 || zBoundMin == 0 || xBoundMax == 0 || zBoundMax == 0) return true;
+ int xDiff = xBoundMax - xBoundMin;
+ int zDiff = zBoundMax - zBoundMin;
+ return xDiff < 5 || zDiff < 5;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/configuration/Maps.java b/src/main/java/dev/tylerm/khs/configuration/Maps.java
new file mode 100644
index 0000000..54c6d5b
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/configuration/Maps.java
@@ -0,0 +1,158 @@
+package dev.tylerm.khs.configuration;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.util.Location;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import com.cryptomorin.xseries.XMaterial;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class Maps {
+
+ private static final HashMap<String, Map> MAPS = new HashMap<>();
+
+ @Nullable
+ public static Map getMap(String name) {
+ return MAPS.get(name);
+ }
+
+ @Nullable
+ public static Map getRandomMap() {
+ Optional<Map> map;
+ if(MAPS.values().size() > 0) {
+ Collection<Map> setupMaps = MAPS.values().stream().filter(m -> !m.isNotSetup()).collect(Collectors.toList());
+ if(setupMaps.size() < 1) {
+ return null;
+ }
+ map = setupMaps.stream().skip(new Random().nextInt(setupMaps.size())).findFirst();
+ } else {
+ map = Optional.empty();
+ }
+ return map.orElse(null);
+ }
+
+ public static void setMap(String name, Map map) {
+ MAPS.put(name, map);
+ saveMaps();
+ }
+
+ public static boolean removeMap(String name) {
+ boolean status = MAPS.remove(name) != null;
+ saveMaps();
+ return status;
+ }
+
+ @NotNull
+ public static Collection<Map> getAllMaps() {
+ return MAPS.values();
+ }
+
+ public static void loadMaps() {
+
+ ConfigManager manager = ConfigManager.create("maps.yml");
+
+ ConfigurationSection maps = manager.getConfigurationSection("maps");
+ if(maps == null) return;
+ Set<String> keys = maps.getKeys(false);
+ if(keys == null) return;
+
+ MAPS.clear();
+ for(String key : keys) {
+ MAPS.put(key, parseMap(maps, key));
+ }
+
+ }
+
+ private static Map parseMap(ConfigurationSection maps, String name) {
+ ConfigurationSection data = maps.getConfigurationSection(name);
+ if(data == null) return null;
+ Map map = new Map(name);
+ Main.getInstance().getLogger().info("Loading map: " + name + "...");
+ map.setSpawn(getSpawn(data, "game"));
+ map.setLobby(getSpawn(data, "lobby"));
+ map.setSeekerLobby(getSpawn(data, "seeker"));
+ map.setBoundMin(data.getInt("bounds.min.x"), data.getInt("bounds.min.z"));
+ map.setBoundMax(data.getInt("bounds.max.x"), data.getInt("bounds.max.z"));
+ map.setWorldBorderData(
+ data.getInt("worldborder.pos.x"),
+ data.getInt("worldborder.pos.z"),
+ data.getInt("worldborder.size"),
+ data.getInt("worldborder.delay"),
+ data.getInt("worldborder.change")
+ );
+ List<String> blockhunt = data.getStringList("blockhunt.blocks");
+ if(blockhunt == null) blockhunt = new ArrayList<>();
+ map.setBlockhunt(
+ data.getBoolean("blockhunt.enabled"),
+ blockhunt
+ .stream()
+ .map(XMaterial::matchXMaterial)
+ .filter(Optional::isPresent)
+ .map(e -> e.get().parseMaterial())
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList())
+ );
+ return map;
+ }
+
+ private static Location getSpawn(ConfigurationSection data, String spawn) {
+ String world = data.getString("spawns."+spawn+".world");
+ double x = data.getDouble("spawns."+spawn+".x");
+ double y = data.getDouble("spawns."+spawn+".y");
+ double z = data.getDouble("spawns."+spawn+".z");
+ return new Location(world, x, y, z);
+ }
+
+ private static void saveMaps() {
+
+ ConfigManager manager = ConfigManager.create("maps.yml");
+ ConfigurationSection maps = new YamlConfiguration();
+
+ for(Map map : MAPS.values()) {
+ ConfigurationSection data = new YamlConfiguration();
+ saveSpawn(data, map.getSpawn(), "game", map);
+ saveSpawn(data, map.getLobby(), "lobby", map);
+ saveSpawn(data, map.getSeekerLobby(), "seeker", map);
+ data.set("bounds.min.x", map.getBoundsMin().getX());
+ data.set("bounds.min.z", map.getBoundsMin().getZ());
+ data.set("bounds.max.x", map.getBoundsMax().getX());
+ data.set("bounds.max.z", map.getBoundsMax().getZ());
+ data.set("worldborder.pos.x", map.getWorldBorderPos().getX());
+ data.set("worldborder.pos.z", map.getWorldBorderPos().getZ());
+ data.set("worldborder.pos.size", map.getWorldBorderData().getX());
+ data.set("worldborder.pos.delay", map.getWorldBorderData().getY());
+ data.set("worldborder.pos.change", map.getWorldBorderData().getZ());
+ data.set("blockhunt.enabled", map.isBlockHuntEnabled());
+ data.set("blockhunt.blocks", map.getBlockHunt().stream().map(Material::name).collect(Collectors.toList()));
+ maps.set(map.getName(), data);
+ }
+
+ manager.set("maps", maps);
+ manager.overwriteConfig();
+
+ }
+
+ private static void saveSpawn(ConfigurationSection data, Location spawn, String name, Map map) {
+ String worldName = getWorldName(name, map);
+ data.set("spawns." + name + ".world", worldName);
+ data.set("spawns." + name + ".x", spawn.getX());
+ data.set("spawns." + name + ".y", spawn.getY());
+ data.set("spawns." + name + ".z", spawn.getZ());
+ }
+
+ private static String getWorldName(String name, Map map) {
+ switch (name) {
+ case "game": return map.getSpawnName();
+ case "lobby": return map.getLobbyName();
+ case "seeker": return map.getSeekerLobbyName();
+ default: return null;
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/Database.java b/src/main/java/dev/tylerm/khs/database/Database.java
new file mode 100644
index 0000000..42c4798
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/Database.java
@@ -0,0 +1,107 @@
+package dev.tylerm.khs.database;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.UUID;
+
+import com.google.common.io.ByteStreams;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Config;
+import dev.tylerm.khs.database.connections.DatabaseConnection;
+import dev.tylerm.khs.database.connections.MySQLConnection;
+import dev.tylerm.khs.database.connections.SQLiteConnection;
+
+public class Database {
+
+ private final GameDataTable playerInfo;
+ private final NameDataTable nameInfo;
+ private final InventoryTable inventoryInfo;
+ private final DatabaseConnection connection;
+
+ public Database(){
+
+ if(Config.databaseType.equalsIgnoreCase("SQLITE")) {
+ Main.getInstance().getLogger().info("SQLITE database chosen");
+ connection = new SQLiteConnection();
+ } else if(Config.databaseType.equalsIgnoreCase("MYSQL")) {
+ Main.getInstance().getLogger().info("MYSQL database chosen");
+ connection = new MySQLConnection();
+ } else {
+ throw new IllegalArgumentException("Invalid database type: " + Config.databaseType);
+ }
+
+ playerInfo = new GameDataTable(this);
+
+ nameInfo = new NameDataTable(this);
+
+ inventoryInfo = new InventoryTable(this);
+
+ LegacyTable legacyTable = new LegacyTable(this);
+ if(legacyTable.exists()){
+ if(legacyTable.copyData()){
+ if(!legacyTable.drop()){
+ Main.getInstance().getLogger().severe("Failed to drop old legacy table: player_info. Some data may be duplicated!");
+ }
+ }
+ }
+ }
+
+ public GameDataTable getGameData(){
+ return playerInfo;
+ }
+
+ public NameDataTable getNameData() { return nameInfo; }
+
+ public InventoryTable getInventoryData() { return inventoryInfo; }
+
+ protected Connection connect() {
+ Connection conn = null;
+ try {
+ conn = connection.connect();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe(e.getMessage());
+ e.printStackTrace();
+ }
+ return conn;
+ }
+
+ protected byte[] encodeUUID(UUID uuid) {
+ try {
+ byte[] bytes = new byte[16];
+ ByteBuffer.wrap(bytes)
+ .putLong(uuid.getMostSignificantBits())
+ .putLong(uuid.getLeastSignificantBits());
+ InputStream is = new ByteArrayInputStream(bytes);
+ byte[] result = new byte[is.available()];
+ if (is.read(result) == -1) {
+ Main.getInstance().getLogger().severe("IO Error: Failed to read bytes from input stream");
+ return new byte[0];
+ }
+ return result;
+ } catch (IOException e) {
+ Main.getInstance().getLogger().severe("IO Error: " + e.getMessage());
+ return new byte[0];
+ }
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ protected UUID decodeUUID(byte[] bytes) {
+ InputStream is = new ByteArrayInputStream(bytes);
+ ByteBuffer buffer = ByteBuffer.allocate(16);
+ try {
+ buffer.put(ByteStreams.toByteArray(is));
+ ((Buffer)buffer).flip();
+ return new UUID(buffer.getLong(), buffer.getLong());
+ } catch (IOException e) {
+ Main.getInstance().getLogger().severe("IO Error: " + e.getMessage());
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/GameDataTable.java b/src/main/java/dev/tylerm/khs/database/GameDataTable.java
new file mode 100644
index 0000000..ed54baa
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/GameDataTable.java
@@ -0,0 +1,221 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2021-2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.database.util.PlayerInfo;
+import dev.tylerm.khs.game.Board;
+import dev.tylerm.khs.game.util.WinType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.sql.*;
+import java.util.*;
+
+public class GameDataTable {
+
+ private final Map<UUID, PlayerInfo> CACHE = new HashMap<>();
+ private final Database database;
+
+ protected GameDataTable(Database database) {
+
+ String sql = "CREATE TABLE IF NOT EXISTS hs_data (\n"
+ + " uuid BINARY(16) PRIMARY KEY,\n"
+ + " hider_wins int NOT NULL,\n"
+ + " seeker_wins int NOT NULL,\n"
+ + " hider_games int NOT NULL,\n"
+ + " seeker_games int NOT NULL,\n"
+ + " hider_kills int NOT NULL,\n"
+ + " seeker_kills int NOT NULL,\n"
+ + " hider_deaths int NOT NULL,\n"
+ + " seeker_deaths int NOT NULL\n"
+ + ");";
+
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ this.database = database;
+ }
+
+ @Nullable
+ public PlayerInfo getInfo(@Nullable UUID uuid) {
+ if (uuid == null) return null;
+ if(CACHE.containsKey(uuid)) return CACHE.get(uuid);
+ String sql = "SELECT * FROM hs_data WHERE uuid = ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ PlayerInfo info = new PlayerInfo(
+ uuid,
+ rs.getInt("hider_wins"),
+ rs.getInt("seeker_wins"),
+ rs.getInt("hider_games"),
+ rs.getInt("seeker_games"),
+ rs.getInt("hider_kills"),
+ rs.getInt("seeker_kills"),
+ rs.getInt("hider_deaths"),
+ rs.getInt("seeker_deaths")
+ );
+ rs.close();
+ CACHE.put(uuid, info);
+ return info;
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Nullable
+ public PlayerInfo getInfoRanking(@NotNull String order, int place) {
+ String sql = "SELECT * FROM hs_data ORDER BY "+order+" DESC LIMIT 1 OFFSET ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, place-1);
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ UUID uuid = database.decodeUUID(rs.getBytes("uuid"));
+ PlayerInfo info = new PlayerInfo(
+ uuid,
+ rs.getInt("hider_wins"),
+ rs.getInt("seeker_wins"),
+ rs.getInt("hider_games"),
+ rs.getInt("seeker_games"),
+ rs.getInt("hider_kills"),
+ rs.getInt("seeker_kills"),
+ rs.getInt("hider_deaths"),
+ rs.getInt("seeker_deaths")
+ );
+ rs.close();
+ CACHE.put(uuid, info);
+ return info;
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Nullable
+ public List<PlayerInfo> getInfoPage(int page) {
+ String sql = "SELECT * FROM hs_data ORDER BY (hider_wins + seeker_wins) DESC LIMIT 10 OFFSET ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, (page-1)*10);
+ ResultSet rs = statement.executeQuery();
+ List<PlayerInfo> infoList = new ArrayList<>();
+ while(rs.next()) {
+ PlayerInfo info = new PlayerInfo(
+ database.decodeUUID(rs.getBytes("uuid")),
+ rs.getInt("hider_wins"),
+ rs.getInt("seeker_wins"),
+ rs.getInt("hider_games"),
+ rs.getInt("seeker_games"),
+ rs.getInt("hider_kills"),
+ rs.getInt("seeker_kills"),
+ rs.getInt("hider_deaths"),
+ rs.getInt("seeker_deaths")
+ );
+ infoList.add(info);
+ }
+ rs.close();
+ return infoList;
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Nullable
+ public Integer getRanking(@NotNull String order, @NotNull UUID uuid) {
+ String sql = "SELECT count(*) AS total FROM hs_data WHERE "+order+" >= (SELECT "+order+" FROM hs_data WHERE uuid = ?) AND "+order+" > 0;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ return rs.getInt("total");
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void addWins(
+ @NotNull Board board,
+ @NotNull List<UUID> uuids,
+ @NotNull List<UUID> winners,
+ @NotNull Map<UUID,Integer> hider_kills,
+ @NotNull Map<UUID,Integer> hider_deaths,
+ @NotNull Map<UUID,Integer> seeker_kills,
+ @NotNull Map<UUID,Integer> seeker_deaths,
+ @NotNull WinType type
+ ) {
+ for(UUID uuid : uuids) {
+ PlayerInfo info = getInfo(uuid);
+ if(info == null){
+ info = new PlayerInfo(uuid, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+ updateInfo(
+ database.encodeUUID(info.getUniqueId()),
+ info.getHiderWins() + (winners.contains(uuid) && type == WinType.HIDER_WIN ? 1 : 0),
+ info.getSeekerWins() + (winners.contains(uuid) && type == WinType.SEEKER_WIN ? 1 : 0),
+ info.getHiderGames() + (board.isHider(uuid) || (board.isSeeker(uuid) && winners.contains(uuid)) ? 1 : 0),
+ info.getSeekerGames() + (board.isSeeker(uuid) && winners.contains(uuid) ? 1 : 0),
+ info.getHiderKills() + hider_kills.getOrDefault(uuid, 0),
+ info.getSeekerKills() + seeker_kills.getOrDefault(uuid, 0),
+ info.getHiderDeaths() + hider_deaths.getOrDefault(uuid, 0),
+ info.getSeekerDeaths() + seeker_deaths.getOrDefault(uuid, 0)
+ );
+ }
+ }
+
+ protected void updateInfo(byte[] uuid, int hider_wins, int seeker_wins, int hider_games, int seeker_games, int hider_kills, int seeker_kills, int hider_deaths, int seeker_deaths){
+ String sql = "REPLACE INTO hs_data (uuid, hider_wins, seeker_wins, hider_games, seeker_games, hider_kills, seeker_kills, hider_deaths, seeker_deaths) VALUES (?,?,?,?,?,?,?,?,?)";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, uuid);
+ statement.setInt(2, hider_wins);
+ statement.setInt(3, seeker_wins);
+ statement.setInt(4, hider_games);
+ statement.setInt(5, seeker_games);
+ statement.setInt(6, hider_kills);
+ statement.setInt(7, seeker_kills);
+ statement.setInt(8, hider_deaths);
+ statement.setInt(9, seeker_deaths);
+ statement.execute();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ CACHE.remove(database.decodeUUID(uuid));
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/InventoryTable.java b/src/main/java/dev/tylerm/khs/database/InventoryTable.java
new file mode 100644
index 0000000..3fc62fb
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/InventoryTable.java
@@ -0,0 +1,103 @@
+package dev.tylerm.khs.database;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.util.io.BukkitObjectInputStream;
+import org.bukkit.util.io.BukkitObjectOutputStream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.sql.*;
+import java.util.UUID;
+
+public class InventoryTable {
+
+ private final Database database;
+
+ protected InventoryTable(Database database) {
+
+ String sql = "CREATE TABLE IF NOT EXISTS hs_inventory (\n"
+ + " uuid BINARY(16) NOT NULL,\n"
+ + " inventory TEXT NOT NULL,\n"
+ + " PRIMARY KEY (uuid)\n"
+ + ");";
+
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ this.database = database;
+ }
+
+ @Nullable
+ public ItemStack[] getInventory(@NotNull UUID uuid) {
+ String sql = "SELECT * FROM hs_inventory WHERE uuid = ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ String data = rs.getString("inventory");
+ if(data == null) return null;
+ return itemStackArrayFromBase64(data);
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ } catch (IOException e) {
+ Main.getInstance().getLogger().severe("IO Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void saveInventory(@NotNull UUID uuid, @NotNull ItemStack[] itemArray) {
+ String sql = "REPLACE INTO hs_inventory (uuid, inventory) VALUES (?,?)";
+ String data = itemStackArrayToBase64(itemArray);
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ statement.setString(2, data);
+ statement.execute();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ private String itemStackArrayToBase64(ItemStack[] itemArray) throws IllegalStateException {
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
+
+ dataOutput.writeObject(itemArray);
+
+ dataOutput.close();
+
+ return Base64Coder.encodeLines(outputStream.toByteArray());
+ } catch (Exception e) {
+ throw new IllegalStateException("Error whilst saving items, Please contact the developer", e);
+ }
+ }
+
+ private ItemStack[] itemStackArrayFromBase64(String data) throws IOException {
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
+ BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
+
+ ItemStack[] itemArray = (ItemStack[]) dataInput.readObject();
+
+ dataInput.close();
+ return itemArray;
+ } catch (ClassNotFoundException e) {
+ throw new IOException("Error whilst loading items, Please contact the developer", e);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/LegacyTable.java b/src/main/java/dev/tylerm/khs/database/LegacyTable.java
new file mode 100644
index 0000000..7bf079c
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/LegacyTable.java
@@ -0,0 +1,101 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database;
+
+import dev.tylerm.khs.database.util.LegacyPlayerInfo;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LegacyTable {
+
+ private final Database database;
+ private final boolean exists;
+
+ protected LegacyTable(Database database) {
+
+ String sql = "SELECT * FROM player_info LIMIT 1;";
+
+ boolean check;
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ ResultSet resultSet = statement.executeQuery(sql);
+ check = resultSet.next();
+ } catch (SQLException e) {
+ check = false;
+ }
+
+ this.exists = check;
+ this.database = database;
+ }
+
+ public boolean exists(){
+ return exists;
+ }
+
+ public boolean copyData(){
+ String sql = "SELECT * FROM player_info;";
+ List<LegacyPlayerInfo> legacyPlayerInfoList = new ArrayList<>();
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ ResultSet resultSet = statement.executeQuery(sql);
+ while(resultSet.next()){
+ legacyPlayerInfoList.add(new LegacyPlayerInfo(
+ resultSet.getBytes("uuid"),
+ resultSet.getInt("hider_wins"),
+ resultSet.getInt("seeker_wins"),
+ resultSet.getInt("games_played")
+ ));
+ }
+ resultSet.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ for(LegacyPlayerInfo legacyInfo : legacyPlayerInfoList){
+ database.getGameData().updateInfo(
+ legacyInfo.getUniqueId(),
+ legacyInfo.getHiderWins(),
+ legacyInfo.getSeekerWins(),
+ legacyInfo.getGamesPlayer() - legacyInfo.getSeekerWins(),
+ legacyInfo.getSeekerWins(),
+ 0,
+ 0,
+ 0,
+ 0
+ );
+ }
+ return true;
+ }
+
+ public boolean drop(){
+ String sql = "DROP table player_info";
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ statement.execute(sql);
+ return true;
+ } catch (SQLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/NameDataTable.java b/src/main/java/dev/tylerm/khs/database/NameDataTable.java
new file mode 100644
index 0000000..1ce7143
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/NameDataTable.java
@@ -0,0 +1,113 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.sql.*;
+import java.util.UUID;
+
+public class NameDataTable {
+
+ private final Database database;
+
+ protected NameDataTable(Database database) {
+
+ String sql = "CREATE TABLE IF NOT EXISTS hs_names (\n"
+ + " uuid BINARY(16) NOT NULL,\n"
+ + " name VARCHAR(48) NOT NULL,\n"
+ + " PRIMARY KEY (uuid,name)\n"
+ + ");";
+
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ this.database = database;
+ }
+
+ @Nullable
+ public String getName(@NotNull UUID uuid) {
+ String sql = "SELECT * FROM hs_names WHERE uuid = ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ return rs.getString("name");
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ OfflinePlayer retry = Bukkit.getOfflinePlayer(uuid);
+ if(retry != null && retry.getName() != null){
+ this.update(uuid, retry.getName());
+ return retry.getName();
+ }
+ return null;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Nullable
+ public UUID getUUID(@NotNull String name) {
+ String sql = "SELECT * FROM hs_names WHERE name = ?;";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setString(1, name);
+ ResultSet rs = statement.executeQuery();
+ if (rs.next()) {
+ return database.decodeUUID(rs.getBytes("uuid"));
+ }
+ rs.close();
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ OfflinePlayer retry = Bukkit.getOfflinePlayer(name);
+ if(retry != null){
+ this.update(retry.getUniqueId(), name);
+ return retry.getUniqueId();
+ }
+ return null;
+ }
+
+ public boolean update(@NotNull UUID uuid, @NotNull String name){
+ String sql = "REPLACE INTO hs_names (uuid, name) VALUES (?,?)";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setBytes(1, database.encodeUUID(uuid));
+ statement.setString(2, name);
+ statement.execute();
+ statement.close();
+ return true;
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe("SQL Error: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/connections/DatabaseConnection.java b/src/main/java/dev/tylerm/khs/database/connections/DatabaseConnection.java
new file mode 100644
index 0000000..7dd8c01
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/connections/DatabaseConnection.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database.connections;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public interface DatabaseConnection {
+
+ Connection connect() throws SQLException;
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/connections/MySQLConnection.java b/src/main/java/dev/tylerm/khs/database/connections/MySQLConnection.java
new file mode 100644
index 0000000..0f7ce30
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/connections/MySQLConnection.java
@@ -0,0 +1,65 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database.connections;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Config;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+public class MySQLConnection implements DatabaseConnection {
+
+ private final HikariDataSource ds;
+
+ public MySQLConnection(){
+
+ HikariConfig config = new HikariConfig();
+
+ Main.getInstance().getLogger().info("Database host: " + Config.databaseHost);
+ Main.getInstance().getLogger().info("Database port: " + Config.databasePort);
+ Main.getInstance().getLogger().info("Database user: " + Config.databaseUser);
+ Main.getInstance().getLogger().info("Database pass: xxxxxxxxxxx");
+ Main.getInstance().getLogger().info("Database name: " + Config.databaseName);
+
+
+ config.setDriverClassName(org.mariadb.jdbc.Driver.class.getName());
+ config.setJdbcUrl("jdbc:mariadb://"+ Config.databaseHost+":"+ Config.databasePort+"/"+ Config.databaseName.trim());
+ config.addDataSourceProperty("cachePrepStmts", "true");
+ config.addDataSourceProperty("prepStmtCacheSize", "250");
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+ config.addDataSourceProperty("user", Config.databaseUser);
+ config.addDataSourceProperty("password", Config.databasePass);
+ config.addDataSourceProperty("autoCommit", "true");
+ config.setAutoCommit(true);
+ config.setMaximumPoolSize(20);
+
+ ds = new HikariDataSource(config);
+
+ }
+
+ @Override
+ public Connection connect() throws SQLException {
+ return ds.getConnection();
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/connections/SQLiteConnection.java b/src/main/java/dev/tylerm/khs/database/connections/SQLiteConnection.java
new file mode 100644
index 0000000..70a31fd
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/connections/SQLiteConnection.java
@@ -0,0 +1,64 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database.connections;
+
+import dev.tylerm.khs.Main;
+import org.sqlite.SQLiteConfig;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public class SQLiteConnection implements DatabaseConnection {
+
+ private final File databaseFile;
+ private final SQLiteConfig config;
+
+ public SQLiteConnection(){
+
+ try {
+ Class.forName("org.sqlite.JDBC");
+ } catch (ClassNotFoundException e) {
+ Main.getInstance().getLogger().severe(e.getMessage());
+ throw new RuntimeException(e.getMessage());
+ }
+
+ databaseFile = new File(Main.getInstance().getDataFolder(), "database.db");
+
+ config = new SQLiteConfig();
+ config.setSynchronous(SQLiteConfig.SynchronousMode.NORMAL);
+ config.setTempStore(SQLiteConfig.TempStore.MEMORY);
+ }
+
+ @Override
+ public Connection connect() {
+ Connection conn = null;
+ try {
+ String url = "jdbc:sqlite:"+databaseFile.getPath();
+ conn = DriverManager.getConnection(url, config.toProperties());
+ } catch (SQLException e) {
+ Main.getInstance().getLogger().severe(e.getMessage());
+ e.printStackTrace();
+ }
+ return conn;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/util/LegacyPlayerInfo.java b/src/main/java/dev/tylerm/khs/database/util/LegacyPlayerInfo.java
new file mode 100644
index 0000000..bbf6e55
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/util/LegacyPlayerInfo.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2021-2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database.util;
+
+public class LegacyPlayerInfo {
+
+ private final byte[] uniqueId;
+ private final int hiderWins;
+ private final int seekerWins;
+ private final int gamesPlayed;
+
+ public LegacyPlayerInfo(byte[] uniqueId, int hiderWins, int seekerWins, int gamesPlayed) {
+ this.uniqueId = uniqueId;
+ this.hiderWins = hiderWins;
+ this.seekerWins = seekerWins;
+ this.gamesPlayed = gamesPlayed;
+ }
+
+ public byte[] getUniqueId() {
+ return uniqueId;
+ }
+
+ public int getHiderWins() {
+ return hiderWins;
+ }
+
+ public int getSeekerWins() {
+ return seekerWins;
+ }
+
+ public int getGamesPlayer() { return gamesPlayed; }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/database/util/PlayerInfo.java b/src/main/java/dev/tylerm/khs/database/util/PlayerInfo.java
new file mode 100644
index 0000000..555a954
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/database/util/PlayerInfo.java
@@ -0,0 +1,84 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2021-2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.database.util;
+
+import java.util.UUID;
+
+public class PlayerInfo {
+
+ private final UUID uniqueId;
+ private final int hiderWins;
+ private final int seekerWins;
+ private final int hiderGames;
+ private final int seekerGames;
+ private final int hiderKills;
+ private final int seekerKills;
+ private final int hiderDeaths;
+ private final int seekerDeaths;
+
+ public PlayerInfo(UUID uniqueId, int hiderWins, int seekerWins, int hiderGames, int seekerGames, int hiderKills, int seekerKills, int hiderDeaths, int seekerDeaths) {
+ this.uniqueId = uniqueId;
+ this.hiderWins = hiderWins;
+ this.seekerWins = seekerWins;
+ this.hiderGames = hiderGames;
+ this.seekerGames = seekerGames;
+ this.hiderKills = hiderKills;
+ this.seekerKills = seekerKills;
+ this.hiderDeaths = hiderDeaths;
+ this.seekerDeaths = seekerDeaths;
+ }
+
+ public UUID getUniqueId() {
+ return uniqueId;
+ }
+
+ public int getHiderWins() {
+ return hiderWins;
+ }
+
+ public int getSeekerWins() {
+ return seekerWins;
+ }
+
+ public int getHiderGames() {
+ return hiderGames;
+ }
+
+ public int getSeekerGames() {
+ return seekerGames;
+ }
+
+ public int getHiderKills() {
+ return hiderKills;
+ }
+
+ public int getSeekerKills() {
+ return seekerKills;
+ }
+
+ public int getHiderDeaths() {
+ return hiderDeaths;
+ }
+
+ public int getSeekerDeaths() {
+ return seekerDeaths;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/Board.java b/src/main/java/dev/tylerm/khs/game/Board.java
new file mode 100644
index 0000000..c02174b
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/Board.java
@@ -0,0 +1,492 @@
+package dev.tylerm.khs.game;
+
+import dev.tylerm.khs.game.events.Border;
+import dev.tylerm.khs.game.events.Glow;
+import dev.tylerm.khs.game.events.Taunt;
+import dev.tylerm.khs.game.util.Status;
+import dev.tylerm.khs.Main;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.scoreboard.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Leaderboard.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Board {
+
+ private enum Type {
+ HIDER,
+ SEEKER,
+ SPECTATOR,
+ }
+
+ private List<UUID> initialSeekers = null;
+ private final Map<UUID, Type> Players = new HashMap<>();
+ private final Map<UUID, CustomBoard> customBoards = new HashMap<>();
+ private final Map<UUID, Integer> hider_kills = new HashMap<>(), seeker_kills = new HashMap<>(), hider_deaths = new HashMap<>(), seeker_deaths = new HashMap<>();
+
+ public boolean contains(Player player) {
+ return Players.containsKey(player.getUniqueId());
+ }
+
+ public boolean containsUUID(UUID uuid) {
+ return Players.containsKey(uuid);
+ }
+
+ public boolean isHider(Player player) {
+ return isHider(player.getUniqueId());
+ }
+
+ public boolean isHider(UUID uuid) {
+ if(!Players.containsKey(uuid)) return false;
+ return Players.get(uuid) == Type.HIDER;
+ }
+
+ public boolean isSeeker(Player player) {
+ return isSeeker(player.getUniqueId());
+ }
+
+ public boolean isSeeker(UUID uuid) {
+ if(!Players.containsKey(uuid)) return false;
+ return Players.get(uuid) == Type.SEEKER;
+ }
+
+ public boolean isSpectator(Player player) {
+ return isSpectator(player.getUniqueId());
+ }
+
+ public boolean isSpectator(UUID uuid) {
+ if(!Players.containsKey(uuid)) return false;
+ return Players.get(uuid) == Type.SPECTATOR;
+ }
+
+ public int sizeHider() {
+ return getHiders().size();
+ }
+
+ public int sizeSeeker() {
+ return getSeekers().size();
+ }
+
+ public int size() {
+ return getPlayers().size();
+ }
+
+ public List<Player> getHiders() {
+ return Players.keySet().stream()
+ .filter(s -> Players.get(s) == Type.HIDER)
+ .map(Bukkit::getPlayer)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ public List<Player> getSeekers() {
+ return Players.keySet().stream()
+ .filter(s -> Players.get(s) == Type.SEEKER)
+ .map(Bukkit::getPlayer)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ public List<Player> getSpectators() {
+ return Players.keySet().stream()
+ .filter(s -> Players.get(s) == Type.SPECTATOR)
+ .map(Bukkit::getPlayer)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ public List<Player> getPlayers() {
+ return Players.keySet().stream()
+ .map(Bukkit::getPlayer)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ public void setInitialSeekers(List<UUID> seekers) {
+ initialSeekers = seekers;
+ }
+
+ public List<Player> getInitialSeekers() {
+ if(initialSeekers == null) return null;
+ return initialSeekers.stream().map(u -> {
+ return Bukkit.getPlayer(u);
+ }).collect(Collectors.toList());
+ }
+
+ public Player getPlayer(UUID uuid) {
+ if(!Players.containsKey(uuid)) {
+ return null;
+ }
+ return Bukkit.getPlayer(uuid);
+ }
+
+ public void addHider(Player player) {
+ Players.put(player.getUniqueId(), Type.HIDER);
+ }
+
+ public void addSeeker(Player player) {
+ Players.put(player.getUniqueId(), Type.SEEKER);
+ }
+
+ public void addSpectator(Player player) {
+ Players.put(player.getUniqueId(), Type.SPECTATOR);
+ }
+
+ public void remove(Player player) {
+ Players.remove(player.getUniqueId());
+ }
+
+ public boolean onSameTeam(Player player1, Player player2) {
+ return Players.get(player1.getUniqueId()) == Players.get(player2.getUniqueId());
+ }
+
+ public void reload() {
+ Players.replaceAll((u, v) -> Type.HIDER);
+ hider_kills.clear();
+ seeker_kills.clear();
+ hider_deaths.clear();
+ seeker_deaths.clear();
+ }
+
+ public void addKill(UUID uuid) {
+ if(Players.get(uuid) == Type.HIDER) {
+ int kills = hider_kills.getOrDefault(uuid, 0);
+ hider_kills.put(uuid, kills + 1);
+ } else if(Players.get(uuid) == Type.SEEKER) {
+ int kills = seeker_kills.getOrDefault(uuid, 0);
+ seeker_kills.put(uuid, kills + 1);
+ }
+ }
+
+ public void addDeath(UUID uuid) {
+ if(Players.get(uuid) == Type.HIDER) {
+ int kills = hider_deaths.getOrDefault(uuid, 0);
+ hider_deaths.put(uuid, kills + 1);
+ } else if(Players.get(uuid) == Type.SEEKER) {
+ int kills = seeker_deaths.getOrDefault(uuid, 0);
+ seeker_deaths.put(uuid, kills + 1);
+ }
+ }
+
+ public Map<UUID, Integer> getHiderKills() {
+ return new HashMap<>(hider_kills);
+ }
+
+ public Map<UUID, Integer> getSeekerKills() {
+ return new HashMap<>(seeker_kills);
+ }
+
+ public Map<UUID, Integer> getHiderDeaths() {
+ return new HashMap<>(hider_deaths);
+ }
+
+ public Map<UUID, Integer> getSeekerDeaths() {
+ return new HashMap<>(seeker_deaths);
+ }
+
+ public void createLobbyBoard(Player player) {
+ createLobbyBoard(player, true);
+ }
+
+ private void createLobbyBoard(Player player, boolean recreate) {
+ CustomBoard board = customBoards.get(player.getUniqueId());
+ if (recreate || board == null) {
+ board = new CustomBoard(player, LOBBY_TITLE);
+ board.updateTeams();
+ }
+ int i=0;
+ for(String line : LOBBY_CONTENTS) {
+ if (line.equalsIgnoreCase("")) {
+ board.addBlank();
+ } else if (line.contains("{COUNTDOWN}")) {
+ if (!lobbyCountdownEnabled) {
+ board.setLine(String.valueOf(i), line.replace("{COUNTDOWN}", COUNTDOWN_ADMINSTART));
+ } else if (Main.getInstance().getGame().getLobbyTime() == -1) {
+ board.setLine(String.valueOf(i), line.replace("{COUNTDOWN}", COUNTDOWN_WAITING));
+ } else {
+ board.setLine(String.valueOf(i), line.replace("{COUNTDOWN}", COUNTDOWN_COUNTING.replace("{AMOUNT}",Main.getInstance().getGame().getLobbyTime()+"")));
+ }
+ } else if (line.contains("{COUNT}")) {
+ board.setLine(String.valueOf(i), line.replace("{COUNT}", getPlayers().size()+""));
+ } else if (line.contains("{SEEKER%}")) {
+ board.setLine(String.valueOf(i), line.replace("{SEEKER%}", getSeekerPercent()+""));
+ } else if (line.contains("{HIDER%}")) {
+ board.setLine(String.valueOf(i), line.replace("{HIDER%}", getHiderPercent() + ""));
+ } else if (line.contains("{MAP}")) {
+ board.setLine(String.valueOf(i), line.replace("{MAP}", getMapName() + ""));
+ } else {
+ board.setLine(String.valueOf(i), line);
+ }
+ i++;
+ }
+ board.display();
+ customBoards.put(player.getUniqueId(), board);
+ }
+
+ public String getMapName() {
+ dev.tylerm.khs.configuration.Map map = Main.getInstance().getGame().getCurrentMap();
+ if(map == null) return "Invalid";
+ else return map.getName();
+ }
+
+ public void createGameBoard(Player player) {
+ createGameBoard(player, true);
+ }
+
+ private void createGameBoard(Player player, boolean recreate) {
+ CustomBoard board = customBoards.get(player.getUniqueId());
+ if (recreate || board == null) {
+ board = new CustomBoard(player, GAME_TITLE);
+ board.updateTeams();
+ }
+
+ int timeLeft = Main.getInstance().getGame().getTimeLeft();
+ Status status = Main.getInstance().getGame().getStatus();
+
+ Taunt taunt = Main.getInstance().getGame().getTaunt();
+ Border worldBorder = Main.getInstance().getGame().getCurrentMap().getWorldBorder();
+ Glow glow = Main.getInstance().getGame().getGlow();
+
+ int i = 0;
+ for(String line : GAME_CONTENTS) {
+ if (line.equalsIgnoreCase("")) {
+ board.addBlank();
+ } else {
+ if (line.contains("{TIME}")) {
+ String value = timeLeft/60 + "m" + timeLeft%60 + "s";
+ board.setLine(String.valueOf(i), line.replace("{TIME}", value));
+ } else if (line.contains("{TEAM}")) {
+ String value = getTeam(player);
+ board.setLine(String.valueOf(i), line.replace("{TEAM}", value));
+ } else if (line.contains("{BORDER}")) {
+ if (!Main.getInstance().getGame().getCurrentMap().isWorldBorderEnabled()) continue;
+ if (status == Status.STARTING) {
+ board.setLine(String.valueOf(i), line.replace("{BORDER}", BORDER_COUNTING.replace("{AMOUNT}", "0")));
+ } else if (!worldBorder.isRunning()) {
+ board.setLine(String.valueOf(i), line.replace("{BORDER}", BORDER_COUNTING.replaceFirst("\\{AMOUNT}", worldBorder.getDelay()/60+"").replaceFirst("\\{AMOUNT}", worldBorder.getDelay()%60+"")));
+ } else {
+ board.setLine(String.valueOf(i), line.replace("{BORDER}", BORDER_DECREASING));
+ }
+ } else if (line.contains("{TAUNT}")) {
+ if (!tauntEnabled) continue;
+ if (taunt == null || status == Status.STARTING) {
+ board.setLine(String.valueOf(i), line.replace("{TAUNT}", TAUNT_COUNTING.replace("{AMOUNT}", "0")));
+ } else if (!tauntLast && sizeHider() == 1) {
+ board.setLine(String.valueOf(i), line.replace("{TAUNT}", TAUNT_EXPIRED));
+ } else if (!taunt.isRunning()) {
+ board.setLine(String.valueOf(i), line.replace("{TAUNT}", TAUNT_COUNTING.replaceFirst("\\{AMOUNT}", taunt.getDelay() / 60 + "").replaceFirst("\\{AMOUNT}", taunt.getDelay() % 60 + "")));
+ } else {
+ board.setLine(String.valueOf(i), line.replace("{TAUNT}", TAUNT_ACTIVE));
+ }
+ } else if (line.contains("{GLOW}")) {
+ if (!glowEnabled) continue;
+ if (glow == null || status == Status.STARTING || !glow.isRunning()) {
+ board.setLine(String.valueOf(i), line.replace("{GLOW}", GLOW_INACTIVE));
+ } else {
+ board.setLine(String.valueOf(i), line.replace("{GLOW}", GLOW_ACTIVE));
+ }
+ } else if (line.contains("{#SEEKER}")) {
+ board.setLine(String.valueOf(i), line.replace("{#SEEKER}", getSeekers().size()+""));
+ } else if (line.contains("{#HIDER}")) {
+ board.setLine(String.valueOf(i), line.replace("{#HIDER}", getHiders().size()+""));
+ } else if (line.contains("{MAP}")) {
+ board.setLine(String.valueOf(i), line.replace("{MAP}", getMapName() + ""));
+ } else {
+ board.setLine(String.valueOf(i), line);
+ }
+ }
+ i++;
+ }
+ board.display();
+ customBoards.put(player.getUniqueId(), board);
+ }
+
+ public void removeBoard(Player player) {
+ ScoreboardManager manager = Bukkit.getScoreboardManager();
+ assert manager != null;
+ player.setScoreboard(manager.getMainScoreboard());
+ customBoards.remove(player.getUniqueId());
+ }
+
+ public void reloadLobbyBoards() {
+ for(Player player : getPlayers())
+ createLobbyBoard(player, false);
+ }
+
+ public void reloadGameBoards() {
+ for(Player player : getPlayers())
+ createGameBoard(player, false);
+ }
+
+ public void reloadBoardTeams() {
+ for(CustomBoard board : customBoards.values())
+ board.updateTeams();
+ }
+
+ private String getSeekerPercent() {
+ int size = size();
+ if (size < 2)
+ return " --";
+ else
+ return " "+(int)(100*(1.0/size));
+ }
+
+ private String getHiderPercent() {
+ int size = size();
+ if (size < 2)
+ return " --";
+ else
+ return " "+(int)(100-100*(1.0/size));
+ }
+
+ private String getTeam(Player player) {
+ if (isHider(player)) return message("HIDER_TEAM_NAME").toString();
+ else if (isSeeker(player)) return message("SEEKER_TEAM_NAME").toString();
+ else if (isSpectator(player)) return message("SPECTATOR_TEAM_NAME").toString();
+ else return ChatColor.WHITE + "UNKNOWN";
+ }
+
+ public void cleanup() {
+ Players.clear();;
+ initialSeekers = null;
+ customBoards.clear();
+ }
+
+}
+
+@SuppressWarnings("deprecation")
+class CustomBoard {
+
+ private final Scoreboard board;
+ private final Objective obj;
+ private final Player player;
+ private final Map<String,Line> LINES;
+ private int blanks;
+ private boolean displayed;
+
+ public CustomBoard(Player player, String title) {
+ ScoreboardManager manager = Bukkit.getScoreboardManager();
+ assert manager != null;
+ this.board = manager.getNewScoreboard();
+ this.LINES = new HashMap<>();
+ this.player = player;
+ if (Main.getInstance().supports(13)) {
+ this.obj = board.registerNewObjective(
+ "Scoreboard", "dummy", ChatColor.translateAlternateColorCodes('&', title));
+ } else {
+ this.obj = board.registerNewObjective("Scoreboard", "dummy");
+ this.obj.setDisplayName(ChatColor.translateAlternateColorCodes('&', title));
+ }
+ this.blanks = 0;
+ this.displayed = false;
+ this.updateTeams();
+ }
+
+ public void updateTeams() {
+ try{ board.registerNewTeam("Hider"); } catch (Exception ignored) {}
+ try{ board.registerNewTeam("Seeker"); } catch (Exception ignored) {}
+ Team hiderTeam = board.getTeam("Hider");
+ assert hiderTeam != null;
+ for(String entry : hiderTeam.getEntries())
+ hiderTeam.removeEntry(entry);
+ for(Player player : Main.getInstance().getBoard().getHiders())
+ hiderTeam.addEntry(player.getName());
+ Team seekerTeam = board.getTeam("Seeker");
+ assert seekerTeam != null;
+ for(String entry : seekerTeam.getEntries())
+ seekerTeam.removeEntry(entry);
+ for(Player player : Main.getInstance().getBoard().getSeekers())
+ seekerTeam.addEntry(player.getName());
+ if (Main.getInstance().supports(9)) {
+ if (nameTagsVisible) {
+ hiderTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.FOR_OWN_TEAM);
+ seekerTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.FOR_OTHER_TEAMS);
+ } else {
+ hiderTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.NEVER);
+ seekerTeam.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.NEVER);
+ }
+ hiderTeam.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
+ seekerTeam.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
+ } else {
+ if (nameTagsVisible) {
+ hiderTeam.setNameTagVisibility(NameTagVisibility.HIDE_FOR_OTHER_TEAMS);
+ seekerTeam.setNameTagVisibility(NameTagVisibility.HIDE_FOR_OWN_TEAM);
+ } else {
+ hiderTeam.setNameTagVisibility(NameTagVisibility.NEVER);
+ seekerTeam.setNameTagVisibility(NameTagVisibility.NEVER);
+ }
+ }
+ hiderTeam.setPrefix(message("HIDER_TEAM_NAME").toString() + " " + ChatColor.RESET);
+ seekerTeam.setPrefix(message("SEEKER_TEAM_NAME").toString() + " " + ChatColor.RESET);
+ }
+
+ public void setLine(String key, String message) {
+ Line line = LINES.get(key);
+ if (line == null)
+ addLine(key, ChatColor.translateAlternateColorCodes('&',message));
+ else
+ updateLine(key, ChatColor.translateAlternateColorCodes('&',message));
+ }
+
+ private void addLine(String key, String message) {
+ Score score = obj.getScore(message);
+ score.setScore(LINES.values().size()+1);
+ Line line = new Line(LINES.values().size()+1, message);
+ LINES.put(key, line);
+ }
+
+ public void addBlank() {
+ if (displayed) return;
+ StringBuilder temp = new StringBuilder();
+ for(int i = 0; i <= blanks; i ++)
+ temp.append(ChatColor.RESET);
+ blanks++;
+ addLine("blank"+blanks, temp.toString());
+ }
+
+ private void updateLine(String key, String message) {
+ Line line = LINES.get(key);
+ board.resetScores(line.getMessage());
+ line.setMessage(message);
+ Score newScore = obj.getScore(message);
+
+ newScore.setScore(line.getScore());
+ }
+
+ public void display() {
+ displayed = true;
+ obj.setDisplaySlot(DisplaySlot.SIDEBAR);
+ player.setScoreboard(board);
+ }
+
+}
+
+class Line {
+
+ private final int score;
+ private String message;
+
+ public Line(int score, String message) {
+ this.score = score;
+ this.message = message;
+ }
+
+ public int getScore() {
+ return score;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/Disguiser.java b/src/main/java/dev/tylerm/khs/game/Disguiser.java
new file mode 100644
index 0000000..68166a3
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/Disguiser.java
@@ -0,0 +1,74 @@
+package dev.tylerm.khs.game;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+import dev.tylerm.khs.game.util.Disguise;
+import dev.tylerm.khs.configuration.Map;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+
+public class Disguiser {
+
+ private final HashMap<Player, Disguise> disguises;
+
+ public Disguiser(){
+ this.disguises = new HashMap<>();
+
+ }
+
+ public Disguise getDisguise(Player player){
+ return disguises.get(player);
+ }
+
+ public boolean disguised(Player player) { return disguises.containsKey(player); }
+
+ @Nullable
+ public Disguise getByEntityID(int ID){
+ return disguises.values().stream().filter(disguise -> disguise.getEntityID() == ID).findFirst().orElse(null);
+ }
+
+ @Nullable
+ public Disguise getByHitBoxID(int ID){
+ return disguises.values().stream().filter(disguise -> disguise.getHitBoxID() == ID).findFirst().orElse(null);
+ }
+
+ public void check(){
+ for(HashMap.Entry<Player, Disguise> set : disguises.entrySet()){
+ Disguise disguise = set.getValue();
+ Player player = set.getKey();
+ if(!player.isOnline()) {
+ disguise.remove();
+ disguises.remove(player);
+ } else {
+ disguise.update();
+ }
+ }
+ }
+
+ public void disguise(Player player, Material material, Map map){
+ if(!map.isBlockHuntEnabled()){
+ player.sendMessage(errorPrefix + message("BLOCKHUNT_DISABLED"));
+ return;
+ }
+ if(disguises.containsKey(player)){
+ disguises.get(player).remove();
+ }
+ Disguise disguise = new Disguise(player, material);
+ disguises.put(player, disguise);
+ }
+
+ public void reveal(Player player){
+ if(disguises.containsKey(player))
+ disguises.get(player).remove();
+ disguises.remove(player);
+ }
+
+ public void cleanUp() {
+ disguises.values().forEach(Disguise::remove);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/EntityHider.java b/src/main/java/dev/tylerm/khs/game/EntityHider.java
new file mode 100644
index 0000000..dc4e02f
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/EntityHider.java
@@ -0,0 +1,326 @@
+package dev.tylerm.khs.game;
+
+import static com.comphenix.protocol.PacketType.Play.Server.*;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
+import org.bukkit.plugin.Plugin;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+public class EntityHider implements Listener {
+ protected Table<Integer, Integer, Boolean> observerEntityMap = HashBasedTable.create();
+
+ private static final PacketType[] ENTITY_PACKETS = {
+ ENTITY_EQUIPMENT, ANIMATION, NAMED_ENTITY_SPAWN,
+ COLLECT, SPAWN_ENTITY, SPAWN_ENTITY_EXPERIENCE_ORB,
+ ENTITY_VELOCITY, REL_ENTITY_MOVE, ENTITY_LOOK,
+ ENTITY_TELEPORT, ENTITY_HEAD_ROTATION, ENTITY_STATUS, ATTACH_ENTITY, ENTITY_METADATA,
+ ENTITY_EFFECT, REMOVE_ENTITY_EFFECT, BLOCK_BREAK_ANIMATION
+ };
+
+ public enum Policy {
+ WHITELIST,
+ BLACKLIST,
+ }
+
+ private ProtocolManager manager;
+
+ private final Listener bukkitListener;
+ private final PacketAdapter protocolListener;
+
+ protected final Policy policy;
+
+ public EntityHider(Plugin plugin, Policy policy) {
+ Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
+
+ // Save policy
+ this.policy = policy;
+ this.manager = ProtocolLibrary.getProtocolManager();
+
+ // Register events and packet listener
+ plugin.getServer().getPluginManager().registerEvents(
+ bukkitListener = constructBukkit(), plugin);
+ manager.addPacketListener(
+ protocolListener = constructProtocol(plugin));
+ }
+
+ /**
+ * Set the visibility status of a given entity for a particular observer.
+ * @param observer - the observer player.
+ * @param entityID - ID of the entity that will be hidden or made visible.
+ * @param visible - TRUE if the entity should be made visible, FALSE if not.
+ * @return TRUE if the entity was visible before this method call, FALSE otherwise.
+ */
+ protected boolean setVisibility(Player observer, int entityID, boolean visible) {
+ switch (policy) {
+ case BLACKLIST:
+ // Non-membership means they are visible
+ return !setMembership(observer, entityID, !visible);
+ case WHITELIST:
+ return setMembership(observer, entityID, visible);
+ default :
+ throw new IllegalArgumentException("Unknown policy: " + policy);
+ }
+ }
+
+ /**
+ * Add or remove the given entity and observer entry from the table.
+ * @param observer - the player observer.
+ * @param newEntityId - ID of the entity.
+ * @param member - TRUE if they should be present in the table, FALSE otherwise.
+ * @return TRUE if they already were present, FALSE otherwise.
+ */
+ protected boolean setMembership(Player observer, int newEntityId, boolean member) {
+ int entityID;
+ try {
+ entityID = observer.getEntityId();
+ } catch (Exception e) {
+ return member;
+ }
+ if (member) {
+ return observerEntityMap.put(newEntityId, entityID, true) != null;
+ } else {
+ return observerEntityMap.remove(newEntityId, entityID) != null;
+ }
+ }
+
+ /**
+ * Determine if the given entity and observer is present in the table.
+ * @param observer - the player observer.
+ * @param newEntityID - ID of the entity.
+ * @return TRUE if they are present, FALSE otherwise.
+ */
+ protected boolean getMembership(Player observer, int newEntityID) {
+ int entityID;
+ try {
+ entityID = observer.getEntityId();
+ } catch (Exception e) {
+ return false;
+ }
+ return observerEntityMap.contains(entityID, newEntityID);
+ }
+
+ /**
+ * Determine if a given entity is visible for a particular observer.
+ * @param observer - the observer player.
+ * @param entityID - ID of the entity that we are testing for visibility.
+ * @return TRUE if the entity is visible, FALSE otherwise.
+ */
+ protected boolean isVisible(Player observer, int entityID) {
+ // If we are using a whitelist, presence means visibility - if not, the opposite is the case
+ boolean presence = getMembership(observer, entityID);
+
+ return (policy == Policy.WHITELIST) == presence;
+ }
+
+ /**
+ * Remove the given entity from the underlying map.
+ * @param entity - the entity to remove.
+ */
+ protected void removeEntity(Entity entity) {
+ int entityID;
+ try {
+ entityID = entity.getEntityId();
+ } catch (Exception e) {
+ return;
+ }
+
+ for (Map<Integer, Boolean> maps : observerEntityMap.rowMap().values()) {
+ maps.remove(entityID);
+ }
+ }
+
+ /**
+ * Invoked when a player logs out.
+ * @param player - the player that jused logged out.
+ */
+ protected void removePlayer(Player player) {
+ int entityID;
+ try {
+ entityID = player.getEntityId();
+ } catch (Exception e) {
+ return;
+ }
+ observerEntityMap.rowMap().remove(entityID);
+ }
+
+ /**
+ * Construct the Bukkit event listener.
+ * @return Our listener.
+ */
+ private Listener constructBukkit() {
+ return new Listener() {
+ @EventHandler
+ public void onEntityDeath(EntityDeathEvent e) {
+ removeEntity(e.getEntity());
+ }
+
+ @EventHandler
+ public void onChunkUnload(ChunkUnloadEvent e) {
+ for (Entity entity : e.getChunk().getEntities()) {
+ removeEntity(entity);
+ }
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent e) {
+ removePlayer(e.getPlayer());
+ }
+ };
+ }
+
+ /**
+ * Construct the packet listener that will be used to intercept every entity-related packet.
+ * @param plugin - the parent plugin.
+ * @return The packet listener.
+ */
+ private PacketAdapter constructProtocol(Plugin plugin) {
+ return new PacketAdapter(plugin, ENTITY_PACKETS) {
+ @Override
+ public void onPacketSending(PacketEvent event) {
+ int entityID = event.getPacket().getIntegers().read(0);
+
+ // See if this packet should be cancelled
+ if (!isVisible(event.getPlayer(), entityID)) {
+ event.setCancelled(true);
+ }
+ }
+ };
+ }
+
+ /**
+ * Toggle the visibility status of an entity for a player.
+ * <p>
+ * If the entity is visible, it will be hidden. If it is hidden, it will become visible.
+ * @param observer - the player observer.
+ * @param entity - the entity to toggle.
+ * @return TRUE if the entity was visible before, FALSE otherwise.
+ */
+ @SuppressWarnings("unused")
+ public final boolean toggleEntity(Player observer, Entity entity) {
+ int entityID;
+ try {
+ entityID = observer.getEntityId();
+ } catch (Exception e) {
+ return true;
+ }
+ if (isVisible(observer, entityID)) {
+ return hideEntity(observer, entity);
+ } else {
+ return !showEntity(observer, entity);
+ }
+ }
+
+ /**
+ * Allow the observer to see an entity that was previously hidden.
+ * @param observer - the observer.
+ * @param entity - the entity to show.
+ * @return TRUE if the entity was hidden before, FALSE otherwise.
+ */
+ public final boolean showEntity(Player observer, Entity entity) {
+ validate(observer, entity);
+ int entityID;
+ try {
+ entityID = entity.getEntityId();
+ } catch (Exception e) {
+ return false;
+ }
+ boolean hiddenBefore = !setVisibility(observer, entityID, true);
+
+ // Resend packets
+ if (manager != null && hiddenBefore) {
+ manager.updateEntity(entity, Collections.singletonList(observer));
+ }
+ return hiddenBefore;
+ }
+
+ /**
+ * Prevent the observer from seeing a given entity.
+ * @param observer - the player observer.
+ * @param entity - the entity to hide.
+ * @return TRUE if the entity was previously visible, FALSE otherwise.
+ */
+ public final boolean hideEntity(Player observer, Entity entity) {
+ validate(observer, entity);
+ int entityID;
+ try {
+ entityID = entity.getEntityId();
+ } catch (Exception e) {
+ return true;
+ }
+ boolean visibleBefore = setVisibility(observer, entityID, false);
+
+ if (visibleBefore) {
+ PacketContainer destroyEntity = new PacketContainer(ENTITY_DESTROY);
+ try {
+ destroyEntity.getIntegerArrays().write(0, new int[]{entityID});
+ } catch (Exception e){ return false; }
+ // Make the entity disappear
+ manager.sendServerPacket(observer, destroyEntity);
+ }
+ return visibleBefore;
+ }
+
+ /**
+ * Determine if the given entity has been hidden from an observer.
+ * <p>
+ * Note that the entity may very well be occluded or out of range from the perspective
+ * of the observer. This method simply checks if an entity has been completely hidden
+ * for that observer.
+ * @param observer - the observer.
+ * @param entity - the entity that may be hidden.
+ * @return TRUE if the player may see the entity, FALSE if the entity has been hidden.
+ */
+ @SuppressWarnings("unused")
+ public final boolean canSee(Player observer, Entity entity) {
+ validate(observer, entity);
+ int entityID;
+ try {
+ entityID = entity.getEntityId();
+ } catch (Exception e) {
+ return true;
+ }
+ return isVisible(observer, entityID);
+ }
+
+ private void validate(Player observer, Entity entity) {
+ Preconditions.checkNotNull(observer, "observer cannot be NULL.");
+ Preconditions.checkNotNull(entity, "entity cannot be NULL.");
+ }
+
+ /**
+ * Retrieve the current visibility policy.
+ * @return The current visibility policy.
+ */
+ @SuppressWarnings("unused")
+ public Policy getPolicy() {
+ return policy;
+ }
+
+ @SuppressWarnings("unused")
+ public void close() {
+ if (manager != null) {
+ HandlerList.unregisterAll(bukkitListener);
+ manager.removePacketListener(protocolListener);
+ manager = null;
+ }
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/game/Game.java b/src/main/java/dev/tylerm/khs/game/Game.java
new file mode 100644
index 0000000..ac7e808
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/Game.java
@@ -0,0 +1,411 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2020-2021. Tyler Murphy
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.game;
+
+import com.cryptomorin.xseries.messages.ActionBar;
+import com.cryptomorin.xseries.messages.Titles;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import dev.tylerm.khs.game.events.Glow;
+import dev.tylerm.khs.game.events.Taunt;
+import dev.tylerm.khs.game.listener.RespawnHandler;
+import dev.tylerm.khs.game.util.CountdownDisplay;
+import dev.tylerm.khs.game.util.Status;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.WinType;
+import org.bukkit.*;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Game {
+
+ private final Taunt taunt;
+ private final Glow glow;
+
+ private final Board board;
+
+ private Status status;
+
+ private Map currentMap;
+
+ private int gameTick;
+ private int lobbyTimer;
+ private int startingTimer;
+ private int gameTimer;
+ private boolean hiderLeft;
+
+ private Random random;
+
+ public Game(Map map, Board board) {
+
+ this.currentMap = map;
+
+ this.taunt = new Taunt();
+ this.glow = new Glow();
+
+ this.status = Status.STANDBY;
+
+ this.board = board;
+
+ this.gameTick = 0;
+ this.lobbyTimer = -1;
+ this.startingTimer = -1;
+ this.gameTimer = 0;
+ this.hiderLeft = false;
+
+ this.random = new Random();
+ }
+
+ public Status getStatus(){
+ return status;
+ }
+
+ public int getTimeLeft(){
+ return gameTimer;
+ }
+
+ public int getLobbyTime(){
+ return lobbyTimer;
+ }
+
+ public Glow getGlow(){
+ return glow;
+ }
+
+ public Taunt getTaunt(){
+ return taunt;
+ }
+
+ public void start() {
+ List<Player> seekers = new ArrayList<>(startingSeekerCount);
+ List<Player> pool = board.getPlayers();
+ for (int i = 0; i < startingSeekerCount; i++) {
+ try {
+ int rand = random.nextInt(0, pool.size());
+ seekers.add(pool.remove(rand));
+ } catch (Exception e){
+ Main.getInstance().getLogger().warning("Failed to select random seeker.");
+ return;
+ }
+ }
+ start(seekers);
+ }
+
+ public void start(List<Player> seekers) {
+ if (mapSaveEnabled) currentMap.getWorldLoader().rollback();
+ board.reload();
+ board.setInitialSeekers(seekers.stream().map(Player::getUniqueId).collect(Collectors.toList()));
+ seekers.forEach(seeker -> {
+ board.addSeeker(seeker);
+ PlayerLoader.loadSeeker(seeker, currentMap);
+ });
+ board.getPlayers().forEach(player -> {
+ if(board.isSeeker(player)) return;
+ board.addHider(player);
+ PlayerLoader.loadHider(player, currentMap);
+ });
+ board.getPlayers().forEach(board::createGameBoard);
+ currentMap.getWorldBorder().resetWorldBorder();
+ if (gameLength > 0) gameTimer = gameLength;
+ status = Status.STARTING;
+ startingTimer = hidingTimer;
+ }
+
+ private void stop(WinType type) {
+ status = Status.ENDING;
+ List<UUID> players = board.getPlayers().stream().map(Entity::getUniqueId).collect(Collectors.toList());
+ if (type == WinType.HIDER_WIN) {
+ List<UUID> winners = board.getHiders().stream().map(Entity::getUniqueId).collect(Collectors.toList());
+ Main.getInstance().getDatabase().getGameData().addWins(board, players, winners, board.getHiderKills(), board.getHiderDeaths(), board.getSeekerKills(), board.getSeekerDeaths(), type);
+ } else if (type == WinType.SEEKER_WIN) {
+ List<UUID> winners = new ArrayList<>();
+ board.getInitialSeekers().forEach(p -> {
+ winners.add(p.getUniqueId());
+ });
+ if (!waitTillNoneLeft && board.getHiders().size() == 1) {
+ winners.add(board.getHiders().get(0).getUniqueId());
+ }
+ Main.getInstance().getDatabase().getGameData().addWins(board, players, winners, board.getHiderKills(), board.getHiderDeaths(), board.getSeekerKills(), board.getSeekerDeaths(), type);
+ }
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), this::end, endGameDelay*20);
+ }
+
+ public void end() {
+ board.getPlayers().forEach(PlayerLoader::unloadPlayer);
+ currentMap.getWorldBorder().resetWorldBorder();
+ Map nextMap = Maps.getRandomMap();
+ if(nextMap != null) this.currentMap = nextMap;
+ board.getPlayers().forEach(player -> {
+ if (leaveOnEnd) {
+ board.removeBoard(player);
+ board.remove(player);
+ handleBungeeLeave(player);
+ } else {
+ currentMap.getLobby().teleport(player);
+ board.createLobbyBoard(player);
+ board.addHider(player);
+ PlayerLoader.joinPlayer(player, currentMap);
+ }
+ });
+ RespawnHandler.temp_loc.clear();
+ if (mapSaveEnabled) currentMap.getWorldLoader().unloadMap();
+ board.reloadLobbyBoards();
+ status = Status.ENDED;
+ }
+
+ public void join(Player player) {
+ if (status != Status.STARTING && status != Status.PLAYING) {
+ if(saveInventory) {
+ ItemStack[] data = player.getInventory().getContents();
+ Main.getInstance().getDatabase().getInventoryData().saveInventory(player.getUniqueId(), data);
+ }
+ PlayerLoader.joinPlayer(player, currentMap);
+ board.addHider(player);
+ board.createLobbyBoard(player);
+ board.reloadLobbyBoards();
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(messagePrefix + message("GAME_JOIN").addPlayer(player));
+ else broadcastMessage(messagePrefix + message("GAME_JOIN").addPlayer(player));
+ } else {
+ PlayerLoader.loadSpectator(player, currentMap);
+ board.addSpectator(player);
+ board.createGameBoard(player);
+ player.sendMessage(messagePrefix + message("GAME_JOIN_SPECTATOR"));
+ }
+ }
+
+ public void leave(Player player) {
+ PlayerLoader.unloadPlayer(player);
+ if(saveInventory) {
+ ItemStack[] data = Main.getInstance().getDatabase().getInventoryData().getInventory(player.getUniqueId());
+ try {
+ player.getInventory().setContents(data);
+ } catch (NullPointerException ignored){}
+ }
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(messagePrefix + message("GAME_LEAVE").addPlayer(player));
+ else broadcastMessage(messagePrefix + message("GAME_LEAVE").addPlayer(player));
+ if (board.isHider(player) && status != Status.ENDING && status != Status.STANDBY) {
+ hiderLeft = true;
+ }
+ board.removeBoard(player);
+ board.remove(player);
+ if (status == Status.STANDBY) {
+ board.reloadLobbyBoards();
+ } else {
+ board.reloadGameBoards();
+ board.reloadBoardTeams();
+ }
+ handleBungeeLeave(player);
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ private void handleBungeeLeave(Player player) {
+ if (bungeeLeave) {
+ ByteArrayDataOutput out = ByteStreams.newDataOutput();
+ out.writeUTF("Connect");
+ out.writeUTF(leaveServer);
+ player.sendPluginMessage(Main.getInstance(), "BungeeCord", out.toByteArray());
+ } else {
+ exitPosition.teleport(player);
+ }
+ }
+
+ public void onTick() {
+ if (currentMap == null || currentMap.isNotSetup()) return;
+ if (status == Status.STANDBY) whileWaiting();
+ else if (status == Status.STARTING) whileStarting();
+ else if (status == Status.PLAYING) whilePlaying();
+ gameTick++;
+ }
+
+ private void whileWaiting() {
+ if (!lobbyCountdownEnabled) return;
+ if (lobbyMin <= board.size()) {
+ if (lobbyTimer < 0)
+ lobbyTimer = countdown;
+ if (board.size() >= changeCountdown)
+ lobbyTimer = Math.min(lobbyTimer, 10);
+ if (gameTick % 20 == 0) {
+ lobbyTimer--;
+ board.reloadLobbyBoards();
+ }
+ if (lobbyTimer == 0) {
+ start();
+ }
+ } else {
+ lobbyTimer = -1;
+ if (gameTick % 20 == 0) {
+ board.reloadLobbyBoards();
+ }
+ }
+ }
+
+ private void whileStarting() {
+ if(gameTick % 20 == 0) {
+ if (startingTimer % 5 == 0 || startingTimer < 5) {
+ String message;
+ if (startingTimer == 0) {
+ message = message("START").toString();
+ status = Status.PLAYING;
+ board.getPlayers().forEach(player -> {
+ PlayerLoader.resetPlayer(player, board);
+ if(board.isSeeker(player)){
+ currentMap.getGameSpawn().teleport(player);
+ }
+ });
+ } else if (startingTimer == 1){
+ message = message("START_COUNTDOWN_LAST").addAmount(startingTimer).toString();
+ } else {
+ message = message("START_COUNTDOWN").addAmount(startingTimer).toString();
+ }
+ board.getPlayers().forEach(player -> {
+ if (countdownDisplay == CountdownDisplay.CHAT) {
+ player.sendMessage(messagePrefix + message);
+ } else if (countdownDisplay == CountdownDisplay.ACTIONBAR) {
+ ActionBar.clearActionBar(player);
+ ActionBar.sendActionBar(player, messagePrefix + message);
+ } else if (countdownDisplay == CountdownDisplay.TITLE && startingTimer != 30) {
+ Titles.clearTitle(player);
+ Titles.sendTitle(player, 10, 40, 10, " ", message);
+ }
+ });
+ }
+ startingTimer--;
+ }
+ checkWinConditions();
+ }
+
+ private void whilePlaying() {
+ for(Player hider : board.getHiders()) {
+ int distance = 100, temp = 100;
+ for(Player seeker : board.getSeekers()) {
+ try {
+ temp = (int) hider.getLocation().distance(seeker.getLocation());
+ } catch (Exception e) {
+ //Players in different worlds, NOT OK!!!
+ }
+ if (distance > temp) {
+ distance = temp;
+ }
+ }
+ if (seekerPing) switch(gameTick %10) {
+ case 0:
+ if (distance < seekerPingLevel1) heartbeatSound.play(hider, seekerPingLeadingVolume, seekerPingPitch);
+ if (distance < seekerPingLevel3) ringingSound.play(hider, seekerPingVolume, seekerPingPitch);
+ break;
+ case 3:
+ if (distance < seekerPingLevel1) heartbeatSound.play(hider, seekerPingVolume, seekerPingPitch);
+ if (distance < seekerPingLevel3) ringingSound.play(hider, seekerPingVolume, seekerPingPitch);
+ break;
+ case 6:
+ if (distance < seekerPingLevel3) ringingSound.play(hider, seekerPingVolume, seekerPingPitch);
+ break;
+ case 9:
+ if (distance < seekerPingLevel2) ringingSound.play(hider, seekerPingVolume, seekerPingPitch);
+ break;
+ }
+ }
+ if (gameTick %20 == 0) {
+ if (gameLength > 0) {
+ board.reloadGameBoards();
+ gameTimer--;
+ }
+ if (currentMap.isWorldBorderEnabled()) currentMap.getWorldBorder().update();
+ if (tauntEnabled) taunt.update();
+ if (glowEnabled || alwaysGlow) glow.update();
+ }
+ board.getSpectators().forEach(spectator -> spectator.setFlying(spectator.getAllowFlight()));
+ checkWinConditions();
+ }
+
+ public void broadcastMessage(String message) {
+ for(Player player : board.getPlayers()) {
+ player.sendMessage(message);
+ }
+ }
+
+ public void broadcastTitle(String title, String subtitle) {
+ for (Player player : board.getPlayers()) {
+ Titles.sendTitle(player, 10, 70, 20, title, subtitle);
+ }
+ }
+
+ public boolean isCurrentMapValid() {
+ return currentMap != null && !currentMap.isNotSetup();
+ }
+
+ public boolean checkCurrentMap() {
+ if(currentMap != null && !currentMap.isNotSetup()) return false;
+ this.currentMap = Maps.getRandomMap();
+ return this.currentMap == null;
+ }
+
+ public void setCurrentMap(Map map) {
+ this.currentMap = map;
+ }
+
+ public Map getCurrentMap() {
+ return currentMap;
+ }
+
+ private void checkWinConditions() {
+ int hiderCount = board.sizeHider();
+ if (hiderCount < 1 || (!waitTillNoneLeft && hiderCount < 2)) {
+ if (hiderLeft && dontRewardQuit) {
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_HIDERS_QUIT"));
+ else broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_HIDERS_QUIT"));
+ if (gameOverTitle) broadcastTitle(message("GAME_TITLE_NO_WIN").toString(), message("GAME_GAMEOVER_HIDERS_QUIT").toString());
+ stop(WinType.NONE);
+ } else {
+ if (hiderCount < 1 || waitTillNoneLeft) {
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_HIDERS_FOUND"));
+ else broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_HIDERS_FOUND"));
+ if (gameOverTitle) broadcastTitle(message("GAME_TITLE_SEEKERS_WIN").toString(), message("GAME_GAMEOVER_HIDERS_FOUND").toString());
+ } else {
+ Player hider = board.getHiders().get(0);
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_LAST_HIDER").addPlayer(hider));
+ else broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_LAST_HIDER").addPlayer(hider));
+ if (gameOverTitle) broadcastTitle(message("GAME_TITLE_SINGLE_HIDER_WIN").addPlayer(hider).toString(), message("GAME_SUBTITLE_SINGLE_HIDER_WIN").addPlayer(hider).toString());
+ }
+ stop(WinType.SEEKER_WIN);
+ }
+ } else if (board.sizeSeeker() < 1) {
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(abortPrefix + message("GAME_GAMEOVER_SEEKERS_QUIT"));
+ else broadcastMessage(abortPrefix + message("GAME_GAMEOVER_SEEKERS_QUIT"));
+ if (gameOverTitle) broadcastTitle(message("GAME_TITLE_NO_WIN").toString(), message("GAME_GAMEOVER_SEEKERS_QUIT").toString());
+ stop(dontRewardQuit ? WinType.NONE : WinType.HIDER_WIN);
+ } else if (gameTimer < 1) {
+ if (announceMessagesToNonPlayers) Bukkit.broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_TIME"));
+ else broadcastMessage(gameOverPrefix + message("GAME_GAMEOVER_TIME"));
+ if (gameOverTitle) broadcastTitle(message("GAME_TITLE_HIDERS_WIN").toString(), message("GAME_GAMEOVER_TIME").toString());
+ stop(WinType.HIDER_WIN);
+ }
+ hiderLeft = false;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/PlayerLoader.java b/src/main/java/dev/tylerm/khs/game/PlayerLoader.java
new file mode 100644
index 0000000..2387388
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/PlayerLoader.java
@@ -0,0 +1,187 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.game;
+
+import com.cryptomorin.xseries.messages.Titles;
+import net.md_5.bungee.api.ChatColor;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Items;
+import dev.tylerm.khs.configuration.Map;
+import org.bukkit.GameMode;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeInstance;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Items.HIDER_ITEMS;
+import static dev.tylerm.khs.configuration.Items.SEEKER_ITEMS;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+@SuppressWarnings("deprecation")
+public class PlayerLoader {
+
+ public static void loadHider(Player player, Map map){
+ map.getGameSpawn().teleport(player);
+ loadPlayer(player);
+ player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED,1000000,5,false,false));
+ Titles.sendTitle(player, 10, 70, 20, ChatColor.WHITE + "" + message("HIDER_TEAM_NAME"), ChatColor.WHITE + message("HIDERS_SUBTITLE").toString());
+ if(map.isBlockHuntEnabled()){
+ openBlockHuntPicker(player, map);
+ }
+ }
+
+ public static void loadSeeker(Player player, Map map){
+ map.getGameSeekerLobby().teleport(player);
+ loadPlayer(player);
+ Titles.sendTitle(player, 10, 70, 20, ChatColor.WHITE + "" + message("SEEKER_TEAM_NAME"), ChatColor.WHITE + message("SEEKERS_SUBTITLE").toString());
+ }
+
+ public static void loadSpectator(Player player, Map map){
+ map.getGameSpawn().teleport(player);
+ loadPlayer(player);
+ player.setAllowFlight(true);
+ player.setFlying(true);
+ player.setFallDistance(0.0F);
+ player.getInventory().setItem(flightToggleItemPosition, flightToggleItem);
+ player.getInventory().setItem(teleportItemPosition, teleportItem);
+ Main.getInstance().getBoard().getPlayers().forEach(otherPlayer -> otherPlayer.hidePlayer(player));
+ Titles.sendTitle(player, 10, 70, 20, ChatColor.GRAY + "" + ChatColor.BOLD + "SPECTATING", ChatColor.WHITE + message("SPECTATOR_SUBTITLE").toString());
+ }
+
+ public static void loadDeadHiderSpectator(Player player, Map map) {
+ map.getGameSpawn().teleport(player);
+ loadPlayer(player);
+ player.setAllowFlight(true);
+ player.setFlying(true);
+ player.setFallDistance(0.0F);
+ player.getInventory().setItem(flightToggleItemPosition, flightToggleItem);
+ player.getInventory().setItem(teleportItemPosition, teleportItem);
+ Main.getInstance().getBoard().getPlayers().forEach(otherPlayer -> otherPlayer.hidePlayer(player));
+ }
+
+ public static void resetPlayer(Player player, Board board){
+ if(board.isSpectator(player)) return;
+ loadPlayer(player);
+ if (board.isSeeker(player)) {
+ if (pvpEnabled) {
+ for(int i = 0; i < 9; i++) {
+ if (SEEKER_ITEMS.get(i) == null) continue;
+ player.getInventory().setItem(i, SEEKER_ITEMS.get(i));
+ }
+ if (Items.SEEKER_HELM != null)
+ player.getInventory().setHelmet(Items.SEEKER_HELM);
+ if (Items.SEEKER_CHEST != null)
+ player.getInventory().setChestplate(Items.SEEKER_CHEST);
+ if (Items.SEEKER_LEGS != null)
+ player.getInventory().setLeggings(Items.SEEKER_LEGS);
+ if (Items.SEEKER_BOOTS != null)
+ player.getInventory().setBoots(Items.SEEKER_BOOTS);
+ }
+ for(PotionEffect effect : Items.SEEKER_EFFECTS)
+ player.addPotionEffect(effect);
+ } else if (board.isHider(player)) {
+ if (pvpEnabled) {
+ for(int i = 0; i < 9; i++) {
+ if (HIDER_ITEMS.get(i) == null) continue;
+ player.getInventory().setItem(i, HIDER_ITEMS.get(i));
+ }
+ if (Items.HIDER_HELM != null)
+ player.getInventory().setHelmet(Items.HIDER_HELM);
+ if (Items.HIDER_CHEST != null)
+ player.getInventory().setChestplate(Items.HIDER_CHEST);
+ if (Items.HIDER_LEGS != null)
+ player.getInventory().setLeggings(Items.HIDER_LEGS);
+ if (Items.HIDER_BOOTS != null)
+ player.getInventory().setBoots(Items.HIDER_BOOTS);
+ }
+ for(PotionEffect effect : Items.HIDER_EFFECTS)
+ player.addPotionEffect(effect);
+ if (glowEnabled) {
+ player.getInventory().addItem(glowPowerupItem);
+ }
+ }
+ }
+
+ public static void unloadPlayer(Player player){
+ player.setGameMode(GameMode.ADVENTURE);
+ player.getInventory().clear();
+ Main.getInstance().getDisguiser().reveal(player);
+ for(PotionEffect effect : player.getActivePotionEffects()) {
+ player.removePotionEffect(effect.getType());
+ }
+ if (Main.getInstance().supports(9)) {
+ AttributeInstance attribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
+ if (attribute != null) player.setHealth(attribute.getValue());
+ for(Player temp : Main.getInstance().getBoard().getPlayers()) {
+ Main.getInstance().getGame().getGlow().setGlow(player, temp, false);
+ }
+ } else {
+ player.setHealth(player.getMaxHealth());
+ }
+ Main.getInstance().getBoard().getPlayers().forEach(temp -> {
+ player.showPlayer(temp);
+ temp.showPlayer(player);
+ });
+ player.setAllowFlight(false);
+ player.setFlying(false);
+ player.setFallDistance(0.0F);
+ }
+
+ public static void joinPlayer(Player player, Map map){
+ map.getLobby().teleport(player);
+ loadPlayer(player);
+ if (lobbyStartItem != null && (!lobbyItemStartAdmin || player.hasPermission("hideandseek.start")))
+ player.getInventory().setItem(lobbyItemStartPosition, lobbyStartItem);
+ if (lobbyLeaveItem != null)
+ player.getInventory().setItem(lobbyItemLeavePosition, lobbyLeaveItem);
+ }
+
+ private static void loadPlayer(Player player){
+ player.setFlying(false);
+ player.setAllowFlight(false);
+ player.setGameMode(GameMode.ADVENTURE);
+ player.getInventory().clear();
+ for(PotionEffect effect : player.getActivePotionEffects()) {
+ if(effect.getType().getName().equals("INVISIBILITY") && Main.getInstance().getDisguiser().disguised(player)) continue;
+ player.removePotionEffect(effect.getType());
+ }
+ player.setFoodLevel(20);
+ if (Main.getInstance().supports(9)) {
+ AttributeInstance attribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
+ if (attribute != null) player.setHealth(attribute.getValue());
+ } else {
+ player.setHealth(player.getMaxHealth());
+ }
+ }
+
+ public static void openBlockHuntPicker(Player player, Map map){
+ int slots = ((map.getBlockHunt().size()-1)/9)*9+9;
+ Inventory inventory = Main.getInstance().getServer().createInventory(null, slots, "Select a Block: " + map.getName());
+ for(int i=0;i<map.getBlockHunt().size();i++){
+ inventory.setItem(i, new ItemStack(map.getBlockHunt().get(i)));
+ }
+ player.openInventory(inventory);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/events/Border.java b/src/main/java/dev/tylerm/khs/game/events/Border.java
new file mode 100644
index 0000000..adcb5ce
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/events/Border.java
@@ -0,0 +1,72 @@
+package dev.tylerm.khs.game.events;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Map;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Border {
+
+ private int delay;
+ private boolean running;
+ private final Map map;
+ private int currentSize;
+
+ public Border(Map map) {
+ this.map = map;
+ this.delay = (int) (60 * map.getWorldBorderData().getY());
+ this.currentSize = (int) map.getWorldBorderData().getX();
+ }
+
+ public void update() {
+ if (delay == 30 && !running) {
+ Main.getInstance().getGame().broadcastMessage(worldBorderPrefix + message("WORLDBORDER_WARN"));
+ } else if (delay == 0) {
+ if (running) {
+ delay = (int) (60 * map.getWorldBorderData().getY());
+ running = false;
+ }
+ else decreaseWorldBorder();
+ }
+ delay--;
+ }
+
+ private void decreaseWorldBorder() {
+ if (currentSize == 100) return;
+ if(map.getGameSpawn().load() == null) return;
+ int change = (int) map.getWorldBorderData().getZ();
+ if (currentSize-change < 100) {
+ change = currentSize-100;
+ }
+ running = true;
+ Main.getInstance().getGame().broadcastMessage(worldBorderPrefix + message("WORLDBORDER_DECREASING").addAmount(change));
+ currentSize -= map.getWorldBorderData().getZ();
+ org.bukkit.WorldBorder border = map.getGameSpawn().load().getWorldBorder();
+ border.setSize(border.getSize()-change,30);
+ delay = 30;
+ }
+
+ public void resetWorldBorder() {
+ if(map.getGameSpawn().load() == null) return;
+ org.bukkit.WorldBorder border = map.getGameSpawn().load().getWorldBorder();
+ if (map.isWorldBorderEnabled()) {
+ border.setSize(map.getWorldBorderData().getX());
+ border.setCenter(map.getWorldBorderPos().getX(), map.getWorldBorderPos().getY());
+ currentSize = (int) map.getWorldBorderData().getX();
+ } else {
+ border.setSize(30000000);
+ border.setCenter(0, 0);
+ }
+ delay = (int) (60 * map.getWorldBorderData().getY());
+ }
+
+ public int getDelay() {
+ return delay;
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/game/events/Glow.java b/src/main/java/dev/tylerm/khs/game/events/Glow.java
new file mode 100644
index 0000000..6015f26
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/events/Glow.java
@@ -0,0 +1,72 @@
+package dev.tylerm.khs.game.events;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import dev.tylerm.khs.util.packet.EntityMetadataPacket;
+import dev.tylerm.khs.Main;
+import org.bukkit.entity.Player;
+
+import static dev.tylerm.khs.configuration.Config.*;
+
+public class Glow {
+
+ private static final ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
+
+ private int glowTime;
+ private boolean running;
+
+ public Glow() {
+ this.glowTime = 0;
+ }
+
+ public void onProjectile() {
+ if (glowStackable) glowTime += glowLength;
+ else glowTime = glowLength;
+ running = true;
+ }
+
+ private void sendPackets() {
+ for (Player hider : Main.getInstance().getBoard().getHiders())
+ for (Player seeker : Main.getInstance().getBoard().getSeekers())
+ setGlow(hider, seeker, true);
+ }
+
+ public void update() {
+ if(alwaysGlow){
+ sendPackets();
+ return;
+ }
+ if (running) {
+ sendPackets();
+ glowTime--;
+ glowTime = Math.max(glowTime, 0);
+ if (glowTime == 0) {
+ stopGlow();
+ }
+ }
+ }
+
+ private void stopGlow() {
+ running = false;
+ for (Player hider : Main.getInstance().getBoard().getHiders()) {
+ for (Player seeker : Main.getInstance().getBoard().getSeekers()) {
+ setGlow(hider, seeker, false);
+ }
+ }
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+ public void setGlow(Player player, Player target, boolean glowing) {
+
+ EntityMetadataPacket packet = new EntityMetadataPacket();
+ packet.setEntity(target);
+ packet.setGlow(glowing);
+ packet.writeMetadata();
+ packet.send(player);
+
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/events/Taunt.java b/src/main/java/dev/tylerm/khs/game/events/Taunt.java
new file mode 100644
index 0000000..98a9351
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/events/Taunt.java
@@ -0,0 +1,101 @@
+package dev.tylerm.khs.game.events;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.Color;
+import org.bukkit.FireworkEffect;
+import org.bukkit.World;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Firework;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.meta.FireworkMeta;
+
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Config.tauntDelay;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class Taunt {
+
+ private UUID tauntPlayer;
+ private int delay;
+ private boolean running;
+
+ public Taunt() {
+ this.delay = tauntDelay;
+ }
+
+ public void update() {
+ if (delay == 0) {
+ if (running) launchTaunt();
+ else if (tauntLast || Main.getInstance().getBoard().sizeHider() > 1) executeTaunt();
+ } else {
+ delay--;
+ delay = Math.max(delay, 0);
+ }
+ }
+
+ private void executeTaunt() {
+ Optional<Player> rand = Main.getInstance().getBoard().getHiders().stream().skip(new Random().nextInt(Main.getInstance().getBoard().size())).findFirst();
+ if (!rand.isPresent()) {
+ Main.getInstance().getLogger().warning("Failed to select random seeker.");
+ return;
+ }
+ Player taunted = rand.get();
+ taunted.sendMessage(message("TAUNTED").toString());
+ Main.getInstance().getGame().broadcastMessage(tauntPrefix + message("TAUNT"));
+ tauntPlayer = taunted.getUniqueId();
+ running = true;
+ delay = 30;
+ }
+
+ private void launchTaunt() {
+ Player taunted = Main.getInstance().getBoard().getPlayer(tauntPlayer);
+ if (taunted != null) {
+ if (!Main.getInstance().getBoard().isHider(taunted)) {
+ Main.getInstance().getLogger().info("Taunted played died and is now seeker. Skipping taunt.");
+ tauntPlayer = null;
+ running = false;
+ delay = tauntDelay;
+ return;
+ }
+ World world = taunted.getLocation().getWorld();
+ if (world == null) {
+ Main.getInstance().getLogger().severe("Game world is null while trying to launch taunt.");
+ tauntPlayer = null;
+ running = false;
+ delay = tauntDelay;
+ return;
+ }
+ Firework fw = (Firework) world.spawnEntity(taunted.getLocation(), EntityType.FIREWORK);
+ FireworkMeta fwm = fw.getFireworkMeta();
+ fwm.setPower(4);
+ fwm.addEffect(FireworkEffect.builder()
+ .withColor(Color.BLUE)
+ .withColor(Color.RED)
+ .withColor(Color.YELLOW)
+ .with(FireworkEffect.Type.STAR)
+ .with(FireworkEffect.Type.BALL)
+ .with(FireworkEffect.Type.BALL_LARGE)
+ .flicker(true)
+ .withTrail()
+ .build());
+ fw.setFireworkMeta(fwm);
+ Main.getInstance().getGame().broadcastMessage(tauntPrefix + message("TAUNT_ACTIVATE"));
+ }
+ tauntPlayer = null;
+ running = false;
+ delay = tauntDelay;
+ }
+
+ public int getDelay() {
+ return delay;
+ }
+
+ public boolean isRunning() {
+ return running;
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/game/listener/BlockedCommandHandler.java b/src/main/java/dev/tylerm/khs/game/listener/BlockedCommandHandler.java
new file mode 100644
index 0000000..f99c678
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/BlockedCommandHandler.java
@@ -0,0 +1,36 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.util.Status;
+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.PlayerCommandPreprocessEvent;
+
+import static dev.tylerm.khs.configuration.Config.blockedCommands;
+import static dev.tylerm.khs.configuration.Config.errorPrefix;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class BlockedCommandHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerCommand(PlayerCommandPreprocessEvent event) {
+ Player player = event.getPlayer();
+ String message = event.getMessage();
+ String[] array = message.split(" ");
+ String[] temp = array[0].split(":");
+ for(String handle : blockedCommands) {
+ if (
+ array[0].substring(1).equalsIgnoreCase(handle) && Main.getInstance().getBoard().contains(player) ||
+ temp[temp.length-1].equalsIgnoreCase(handle) && Main.getInstance().getBoard().contains(player)
+ ) {
+ if (Main.getInstance().getGame().getStatus() == Status.STANDBY) return;
+ player.sendMessage(errorPrefix + message("BLOCKED_COMMAND"));
+ event.setCancelled(true);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/ChatHandler.java b/src/main/java/dev/tylerm/khs/game/listener/ChatHandler.java
new file mode 100644
index 0000000..e387a92
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/ChatHandler.java
@@ -0,0 +1,20 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.ChatColor;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+public class ChatHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
+ public void onChat(AsyncPlayerChatEvent event) {
+ if (Main.getInstance().getBoard().isSpectator(event.getPlayer())) {
+ event.setCancelled(true);
+ Main.getInstance().getBoard().getSpectators().forEach(spectator -> spectator.sendMessage(ChatColor.GRAY + "[SPECTATOR] " + event.getPlayer().getName() + ": " + event.getMessage()));
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/DamageHandler.java b/src/main/java/dev/tylerm/khs/game/listener/DamageHandler.java
new file mode 100644
index 0000000..7111d7b
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/DamageHandler.java
@@ -0,0 +1,136 @@
+package dev.tylerm.khs.game.listener;
+
+import com.cryptomorin.xseries.XSound;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.Board;
+import dev.tylerm.khs.game.Game;
+import dev.tylerm.khs.game.PlayerLoader;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Projectile;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class DamageHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onEntityDamage(EntityDamageEvent event) {
+ Board board = Main.getInstance().getBoard();
+ Game game = Main.getInstance().getGame();
+ // If you are not a player, get out of here
+ if (!(event.getEntity() instanceof Player)) return;
+ // Define variables
+ Player player = (Player) event.getEntity();
+ Player attacker = null;
+ // If map is not setup we won't be able to process on it :o
+ if (!game.isCurrentMapValid()) { return; }
+ // If there is an attacker, find them
+ if (event instanceof EntityDamageByEntityEvent) {
+ if (((EntityDamageByEntityEvent) event).getDamager() instanceof Player)
+ attacker = (Player) ((EntityDamageByEntityEvent) event).getDamager();
+ else if (((EntityDamageByEntityEvent) event).getDamager() instanceof Projectile)
+ if (((Projectile) ((EntityDamageByEntityEvent) event).getDamager()).getShooter() instanceof Player)
+ attacker = (Player) ((Projectile) ((EntityDamageByEntityEvent) event).getDamager()).getShooter();
+ }
+ // Makes sure that if there was an attacking player, that the event is allowed for the game
+ if (attacker != null) {
+ // Cancel if one player is in the game but other isn't
+ if ((board.contains(player) && !board.contains(attacker)) || (!board.contains(player) && board.contains(attacker))) {
+ event.setCancelled(true);
+ return;
+ // Ignore event if neither player are in the game
+ } else if (!board.contains(player) && !board.contains(attacker)) {
+ return;
+ // Ignore event if players are on the same team, or one of them is a spectator
+ } else if (board.onSameTeam(player, attacker) || board.isSpectator(player) || board.isSpectator(attacker)) {
+ event.setCancelled(true);
+ return;
+ // Ignore the event if pvp is disabled, and a hider is trying to attack a seeker
+ } else if (!pvpEnabled && board.isHider(attacker) && board.isSeeker(player)) {
+ event.setCancelled(true);
+ return;
+ }
+ // If there was no attacker, if the damaged is not a player, ignore them.
+ } else if (!board.contains(player)) {
+ return;
+ // If there is no attacker, it most of been by natural causes. If pvp is disabled, and config doesn't allow natural causes, cancel event.
+ } else if (!pvpEnabled && !allowNaturalCauses && board.contains(player)) {
+ event.setCancelled(true);
+ return;
+ }
+ // Spectators and cannot take damage
+ if (board.isSpectator(player)) {
+ event.setCancelled(true);
+ if (Main.getInstance().supports(18) && player.getLocation().getBlockY() < -64) {
+ game.getCurrentMap().getGameSpawn().teleport(player);
+ } else if (!Main.getInstance().supports(18) && player.getLocation().getY() < 0) {
+ game.getCurrentMap().getGameSpawn().teleport(player);
+ }
+ return;
+ }
+ // Players cannot take damage while game is not in session
+ if (board.contains(player) && game.getStatus() != Status.PLAYING){
+ event.setCancelled(true);
+ return;
+ }
+ // Check if player dies (pvp mode)
+ if(pvpEnabled && player.getHealth() - event.getFinalDamage() >= 0.5) return;
+ // Handle death event
+ event.setCancelled(true);
+ // Play death effect
+ if (Main.getInstance().supports(9)) {
+ XSound.ENTITY_PLAYER_DEATH.play(player, 1, 1);
+ } else {
+ XSound.ENTITY_PLAYER_HURT.play(player, 1, 1);
+ }
+ // Reveal player if they are disguised
+ Main.getInstance().getDisguiser().reveal(player);
+ // Teleport player to seeker spawn
+ if(delayedRespawn && !respawnAsSpectator){
+ game.getCurrentMap().getGameSeekerLobby().teleport(player);
+ player.sendMessage(messagePrefix + message("RESPAWN_NOTICE").addAmount(delayedRespawnDelay));
+ Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> {
+ if(game.getStatus() == Status.PLAYING){
+ game.getCurrentMap().getGameSpawn().teleport(player);
+ }
+ }, delayedRespawnDelay * 20L);
+ } else {
+ game.getCurrentMap().getGameSpawn().teleport(player);
+ }
+ // Add leaderboard stats
+ board.addDeath(player.getUniqueId());
+ if (attacker != null) board.addKill(attacker.getUniqueId());
+ // Broadcast player death message
+ if (board.isSeeker(player)) {
+ game.broadcastMessage(message("GAME_PLAYER_DEATH").addPlayer(player).toString());
+ } else if (board.isHider(player)) {
+ if (attacker == null) {
+ game.broadcastMessage(message("GAME_PLAYER_FOUND").addPlayer(player).toString());
+ } else {
+ game.broadcastMessage(message("GAME_PLAYER_FOUND_BY").addPlayer(player).addPlayer(attacker).toString());
+ }
+ if (respawnAsSpectator) {
+ board.addSpectator(player);
+ PlayerLoader.loadDeadHiderSpectator(player, game.getCurrentMap());
+ } else {
+ board.addSeeker(player);
+ PlayerLoader.resetPlayer(player, board);
+ }
+ }
+ board.reloadBoardTeams();
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onPlayerDeath(PlayerDeathEvent event){
+ Main.getInstance().getDisguiser().reveal(event.getEntity());
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/DisguiseHandler.java b/src/main/java/dev/tylerm/khs/game/listener/DisguiseHandler.java
new file mode 100644
index 0000000..5883387
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/DisguiseHandler.java
@@ -0,0 +1,101 @@
+package dev.tylerm.khs.game.listener;
+
+import static com.comphenix.protocol.PacketType.Play.Client.*;
+
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.wrappers.EnumWrappers;
+import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.util.Disguise;
+import org.bukkit.Bukkit;
+import org.bukkit.GameMode;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DisguiseHandler implements Listener {
+
+ private static final ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
+
+ public DisguiseHandler(){
+ protocolManager.addPacketListener(createProtocol());
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onMove(PlayerMoveEvent event) {
+ final Player player = event.getPlayer();
+ final Disguise disguise = Main.getInstance().getDisguiser().getDisguise(player);
+ if(disguise == null) return;;
+ if(event.getFrom().distance(event.getTo()) > .1) {
+ disguise.setSolidify(false);
+ }
+ disguise.startSolidifying();
+ }
+
+ private PacketAdapter createProtocol(){
+ return new PacketAdapter(Main.getInstance(), USE_ENTITY) {
+
+ @Override
+ public void onPacketReceiving(PacketEvent event){
+ PacketContainer packet = event.getPacket();
+
+ // only left click attacks
+ EntityUseAction action = packet.getEntityUseActions().getValues().stream().findFirst().orElse(null);
+ if (action == null) return;
+ if (action != EnumWrappers.EntityUseAction.ATTACK) return;
+
+ Player player = event.getPlayer();
+ int id = packet.getIntegers().read(0);
+ Disguise disguise = Main.getInstance().getDisguiser().getByEntityID(id);
+ if(disguise == null) disguise = Main.getInstance().getDisguiser().getByHitBoxID(id);
+ if(disguise == null) return;
+ if(disguise.getPlayer().getGameMode() == GameMode.CREATIVE) return;
+ event.setCancelled(true);
+ handleAttack(disguise, player);
+ }
+ };
+ }
+
+ private final List<Player> debounce = new ArrayList<>();
+
+ private void handleAttack(Disguise disguise, Player seeker){
+
+ if(disguise.getPlayer() == seeker) return;
+
+ double amount;
+ if(Main.getInstance().supports(9)) {
+ amount = seeker.getAttribute(Attribute.GENERIC_ATTACK_DAMAGE).getValue();
+ } else {
+ return; //1.8 is not supported in Blockhunt yet!!!
+ }
+
+ disguise.setSolidify(false);
+ if(debounce.contains(disguise.getPlayer())) return;
+ debounce.add(disguise.getPlayer());
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> {
+ EntityDamageByEntityEvent event =
+ new EntityDamageByEntityEvent(seeker, disguise.getPlayer(), EntityDamageEvent.DamageCause.ENTITY_ATTACK, amount);
+ event.setDamage(amount);
+ disguise.getPlayer().setLastDamageCause(event);
+ Main.getInstance().getServer().getPluginManager().callEvent(event);
+ if(!event.isCancelled()){
+ disguise.getPlayer().damage(amount);
+ disguise.getPlayer().setVelocity(seeker.getLocation().getDirection().setY(.2).multiply(1));
+ }
+
+ }, 0);
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> debounce.remove(disguise.getPlayer()), 10);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/InteractHandler.java b/src/main/java/dev/tylerm/khs/game/listener/InteractHandler.java
new file mode 100644
index 0000000..806b90b
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/InteractHandler.java
@@ -0,0 +1,184 @@
+package dev.tylerm.khs.game.listener;
+
+import com.cryptomorin.xseries.XMaterial;
+import com.cryptomorin.xseries.messages.ActionBar;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.Board;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.inventory.meta.SkullMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Config.glowPowerupItem;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+@SuppressWarnings("deprecation")
+public class InteractHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (!Main.getInstance().getBoard().contains(event.getPlayer())) return;
+ if (event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock() != null && blockedInteracts.contains(event.getClickedBlock().getType().name())) {
+ event.setCancelled(true);
+ return;
+ }
+ ItemStack temp = event.getItem();
+ if (temp == null) return;
+ if (Main.getInstance().getGame().getStatus() == Status.STANDBY)
+ onPlayerInteractLobby(temp, event);
+ if (Main.getInstance().getGame().getStatus() == Status.PLAYING)
+ onPlayerInteractGame(temp, event);
+ if (Main.getInstance().getBoard().isSpectator(event.getPlayer()))
+ onSpectatorInteract(temp, event);
+ }
+
+ private void onPlayerInteractLobby(ItemStack temp, PlayerInteractEvent event) {
+ if (temp.isSimilar(lobbyLeaveItem)) {
+ event.setCancelled(true);
+ Main.getInstance().getGame().leave(event.getPlayer());
+ }
+
+ if (temp.isSimilar(lobbyStartItem) && event.getPlayer().hasPermission("hideandseek.start")) {
+ event.setCancelled(true);
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ event.getPlayer().sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY) {
+ event.getPlayer().sendMessage(errorPrefix + message("GAME_INPROGRESS"));
+ return;
+ }
+ if (Main.getInstance().getBoard().size() < minPlayers) {
+ event.getPlayer().sendMessage(errorPrefix + message("START_MIN_PLAYERS").addAmount(minPlayers));
+ return;
+ }
+ Main.getInstance().getGame().start();
+ }
+ }
+
+ private void onPlayerInteractGame(ItemStack temp, PlayerInteractEvent event) {
+ if (temp.isSimilar(glowPowerupItem)) {
+ if (!glowEnabled) return;
+ Player player = event.getPlayer();
+ if (Main.getInstance().getBoard().isHider(player)) {
+ Main.getInstance().getGame().getGlow().onProjectile();
+ player.getInventory().remove(glowPowerupItem);
+ assert XMaterial.SNOWBALL.parseMaterial() != null;
+ player.getInventory().remove(XMaterial.SNOWBALL.parseMaterial());
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ private void onSpectatorInteract(ItemStack temp, PlayerInteractEvent event){
+ if(temp.isSimilar(flightToggleItem)){
+ boolean isFlying = event.getPlayer().getAllowFlight();
+ event.getPlayer().setAllowFlight(!isFlying);
+ event.getPlayer().setFlying(!isFlying);
+ ActionBar.clearActionBar(event.getPlayer());
+ if(!isFlying){
+ ActionBar.sendActionBar(event.getPlayer(), message("FLYING_ENABLED").toString());
+ } else {
+ ActionBar.sendActionBar(event.getPlayer(), message("FLYING_DISABLED").toString());
+ }
+ return;
+ }
+ if(temp.isSimilar(teleportItem)){
+ // int amount = Main.getInstance().getBoard().getHiders().size() + Main.getInstance().getBoard().getSeekers().size();
+ // Inventory teleportMenu = Main.getInstance().getServer().createInventory(null, 9*(((amount-1)/9)+1), ChatColor.stripColor(teleportItem.getItemMeta().getDisplayName()));
+ // List<String> hider_lore = new ArrayList<>(); hider_lore.add(message("HIDER_TEAM_NAME").toString());
+ // Main.getInstance().getBoard().getHiders().forEach(hider -> teleportMenu.addItem(getSkull(hider, hider_lore)));
+ // List<String> seeker_lore = new ArrayList<>(); seeker_lore.add(message("SEEKER_TEAM_NAME").toString());
+ // Main.getInstance().getBoard().getSeekers().forEach(seeker -> teleportMenu.addItem(getSkull(seeker, seeker_lore)));
+ // event.getPlayer().openInventory(teleportMenu);
+ createSpectatorTeleportPage(event.getPlayer(), 0);
+ }
+ }
+
+ public static void createSpectatorTeleportPage(Player player, int page) {
+
+ if (page < 0) {
+ return;
+ }
+
+ final Board board = Main.getInstance().getBoard();
+ List<Player> players = new ArrayList<>();
+ players.addAll(board.getHiders());
+ players.addAll(board.getSeekers());
+
+ final int page_size = 9 * 5;
+ final int amount = players.size();
+ final int start = page * page_size;
+
+ int page_amount = amount - start;
+
+ if (page_amount < 1) {
+ return;
+ }
+
+ boolean next = false, prev = true;
+
+ if (page_amount > page_size) {
+ page_amount = page_size;
+ next = true;
+ }
+
+ if (page == 0) {
+ prev = false;
+ }
+
+ final int rows = ((amount - 1) / 9) + 2;
+
+ final Inventory teleportMenu = Main.getInstance().getServer().createInventory(null, 9 * rows, ChatColor.stripColor(teleportItem.getItemMeta().getDisplayName()));
+
+ final List<String> hider_lore = new ArrayList<>(); hider_lore.add(message("HIDER_TEAM_NAME").toString());
+ final List<String> seeker_lore = new ArrayList<>(); seeker_lore.add(message("SEEKER_TEAM_NAME").toString());
+
+ for (int i = 0; i < page_amount; i++) {
+ Player plr = players.get(i);
+ teleportMenu.addItem(getSkull(plr, board.isHider(plr) ? hider_lore : seeker_lore));
+ }
+
+ final int lastRow = (rows - 1) * 9;
+ if (prev) {
+ teleportMenu.setItem(lastRow, getPageItem(page - 1));
+ }
+
+ if (next) {
+ teleportMenu.setItem(lastRow + 8, getPageItem(page + 1));
+ }
+
+ player.openInventory(teleportMenu);
+ }
+
+ private static ItemStack getPageItem(int page) {
+ ItemStack prevItem = new ItemStack(XMaterial.ENCHANTED_BOOK.parseMaterial(), page + 1);
+ ItemMeta meta = prevItem.getItemMeta();
+ meta.setDisplayName("Page " + (page+1));
+ prevItem.setItemMeta(meta);
+ return prevItem;
+ }
+
+ private static ItemStack getSkull(Player player, List<String> lore){
+ assert XMaterial.PLAYER_HEAD.parseMaterial() != null;
+ ItemStack playerHead = new ItemStack(XMaterial.PLAYER_HEAD.parseMaterial(), 1, (byte) 3);
+ SkullMeta playerHeadMeta = (SkullMeta) playerHead.getItemMeta();
+ playerHeadMeta.setOwner(player.getName());
+ playerHeadMeta.setDisplayName(player.getName());
+ playerHeadMeta.setLore(lore);
+ playerHead.setItemMeta(playerHeadMeta);
+ return playerHead;
+ }
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/InventoryHandler.java b/src/main/java/dev/tylerm/khs/game/listener/InventoryHandler.java
new file mode 100644
index 0000000..bf40896
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/InventoryHandler.java
@@ -0,0 +1,145 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.game.listener;
+
+import com.cryptomorin.xseries.XMaterial;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.command.map.Debug;
+import dev.tylerm.khs.configuration.Map;
+import dev.tylerm.khs.configuration.Maps;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class InventoryHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onInventoryClick(InventoryClickEvent event) {
+ if (!(event.getWhoClicked() instanceof Player)) return;
+ checkForInventoryMove(event);
+ checkForSpectatorTeleportMenu(event);
+ checkForDebugMenu(event);
+ checkForBlockHuntMenu(event);
+ }
+
+ private void checkForInventoryMove(InventoryClickEvent event){
+ if (Main.getInstance().getBoard().contains((Player) event.getWhoClicked()) && Main.getInstance().getGame().getStatus() == Status.STANDBY) {
+ event.setCancelled(true);
+ }
+ }
+
+ private void checkForSpectatorTeleportMenu(InventoryClickEvent event){
+ Player player = (Player) event.getWhoClicked();
+
+ ItemStack item = event.getCurrentItem();
+ ItemMeta meta = item.getItemMeta();
+ String name = meta.getDisplayName();
+
+ if (Main.getInstance().getBoard().isSpectator(player)) {
+ if (XMaterial.PLAYER_HEAD.isSimilar(item)) {
+ event.setCancelled(true);
+ player.closeInventory();
+ Player clicked = Main.getInstance().getServer().getPlayer(name);
+ if (clicked == null) return;
+ player.teleport(clicked);
+ } else if (XMaterial.ENCHANTED_BOOK.isSimilar(item)) {
+ event.setCancelled(true);
+ player.closeInventory();
+ if (!name.startsWith("Page ")) return;
+ String number_str = name.substring(5);
+ try {
+ int page = Integer.parseInt(number_str);
+ InteractHandler.createSpectatorTeleportPage(player, page - 1);
+ } catch(Exception ignored) {
+ return;
+ }
+ }
+ }
+ }
+
+ private void checkForDebugMenu(InventoryClickEvent event){
+ Player player = (Player) event.getWhoClicked();
+ boolean debug;
+ if(Main.getInstance().supports(14)){
+ debug = event.getView().getTitle().equals("Debug Menu") && player.hasPermission("hideandseek.debug");
+ } else {
+ debug = event.getInventory().getName().equals("Debug Menu") && player.hasPermission("hideandseek.debug");
+ }
+ if (debug){
+ event.setCancelled(true);
+ player.closeInventory();
+ Debug.handleOption(player, event.getRawSlot());
+ }
+ }
+
+ private void checkForBlockHuntMenu(InventoryClickEvent event){
+ boolean test;
+ String mapName;
+ if(Main.getInstance().supports(14)){
+ test = event.getView().getTitle().startsWith("Select a Block: ");
+ if(!test) return;
+ mapName = event.getView().getTitle().substring("Select a Block: ".length());
+ } else {
+ test = event.getInventory().getName().startsWith("Select a Block: ");
+ if(!test) return;
+ mapName = event.getInventory().getName().substring("Select a Block: ".length());
+ }
+ event.setCancelled(true);
+ Map map = Maps.getMap(mapName);
+ if(map == null) return;
+ Material mat = map.getBlockHunt().get(event.getRawSlot());
+ if(mat == null) return;
+ Player player = (Player) event.getWhoClicked();
+ Main.getInstance().getDisguiser().disguise(player, mat, map);
+ player.closeInventory();
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onInventoryClose(InventoryCloseEvent event){
+ if (!(event.getPlayer() instanceof Player)) return;
+ boolean test;
+ String mapName;
+ if(Main.getInstance().supports(14)){
+ test = event.getView().getTitle().startsWith("Select a Block: ");
+ if(!test) return;
+ mapName = event.getView().getTitle().substring("Select a Block: ".length());
+ } else {
+ test = event.getInventory().getName().startsWith("Select a Block: ");
+ if(!test) return;
+ mapName = event.getInventory().getName().substring("Select a Block: ".length());
+ }
+ Map map = Maps.getMap(mapName);
+ if(map == null) return;
+ Material mat = map.getBlockHunt().get(0);
+ if(mat == null) return;
+ Player player = (Player) event.getPlayer();
+ if(Main.getInstance().getDisguiser().disguised(player)) return;
+ Main.getInstance().getDisguiser().disguise(player, mat, map);
+ player.closeInventory();
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/JoinLeaveHandler.java b/src/main/java/dev/tylerm/khs/game/listener/JoinLeaveHandler.java
new file mode 100644
index 0000000..2a4d9cc
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/JoinLeaveHandler.java
@@ -0,0 +1,98 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Items;
+import dev.tylerm.khs.game.PlayerLoader;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.GameMode;
+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 org.bukkit.event.player.PlayerKickEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.inventory.ItemStack;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class JoinLeaveHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ if(!Main.getInstance().getDatabase().getNameData().update(event.getPlayer().getUniqueId(), event.getPlayer().getName())){
+ Main.getInstance().getLogger().warning("Failed to save name data for user: " + event.getPlayer().getName());
+ }
+ Main.getInstance().getBoard().remove(event.getPlayer());
+ removeItems(event.getPlayer());
+ if (Main.getInstance().getGame().checkCurrentMap()) return;
+ if (autoJoin) {
+ if (Main.getInstance().getGame().checkCurrentMap()) {
+ event.getPlayer().sendMessage(errorPrefix + message("GAME_SETUP"));
+ return;
+ }
+ Main.getInstance().getGame().join(event.getPlayer());
+ } else if (teleportToExit) {
+ if (
+ event.getPlayer().getWorld().getName().equals(Main.getInstance().getGame().getCurrentMap().getLobbyName()) ||
+ event.getPlayer().getWorld().getName().equals(Main.getInstance().getGame().getCurrentMap().getGameSpawnName())
+ ) {
+ exitPosition.teleport(event.getPlayer());
+ event.getPlayer().setGameMode(GameMode.ADVENTURE);
+ }
+ } else {
+ if (mapSaveEnabled && event.getPlayer().getWorld().getName().equals(Main.getInstance().getGame().getCurrentMap().getGameSpawnName())) {
+ if (Main.getInstance().getGame().getStatus() != Status.STANDBY && Main.getInstance().getGame().getStatus() != Status.ENDING) {
+ Main.getInstance().getGame().join(event.getPlayer());
+ } else {
+ exitPosition.teleport(event.getPlayer());
+ event.getPlayer().setGameMode(GameMode.ADVENTURE);
+ }
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onQuit(PlayerQuitEvent event) {
+ handleLeave(event.getPlayer());
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onKick(PlayerKickEvent event) {
+ if(event.getReason().equals("Flying is not enabled on this server!")){
+ event.setCancelled(true);
+ return;
+ }
+ handleLeave(event.getPlayer());
+ }
+
+ private void handleLeave(Player player) {
+ if(!Main.getInstance().getBoard().contains(player)) return;
+ PlayerLoader.unloadPlayer(player);
+ Main.getInstance().getBoard().remove(player);
+ if(saveInventory) {
+ ItemStack[] data = Main.getInstance().getDatabase().getInventoryData().getInventory(player.getUniqueId());
+ player.getInventory().setContents(data);
+ }
+ if (Main.getInstance().getGame().getStatus() == Status.STANDBY) {
+ Main.getInstance().getBoard().reloadLobbyBoards();
+ } else {
+ Main.getInstance().getBoard().reloadGameBoards();
+ }
+ }
+
+ private void removeItems(Player player) {
+ for(ItemStack si : Items.SEEKER_ITEMS) {
+ if (si == null) continue;
+ for (ItemStack i : player.getInventory().getContents())
+ if (si.isSimilar(i)) player.getInventory().remove(i);
+ }
+ for(ItemStack hi : Items.HIDER_ITEMS) {
+ if (hi == null) continue;
+ for (ItemStack i : player.getInventory().getContents())
+ if (hi.isSimilar(i)) player.getInventory().remove(i);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/MovementHandler.java b/src/main/java/dev/tylerm/khs/game/listener/MovementHandler.java
new file mode 100644
index 0000000..7d4be5b
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/MovementHandler.java
@@ -0,0 +1,61 @@
+package dev.tylerm.khs.game.listener;
+
+import com.google.common.collect.Sets;
+import dev.tylerm.khs.game.listener.events.PlayerJumpEvent;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Map;
+import org.bukkit.Material;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class MovementHandler implements Listener {
+
+ private final Set<UUID> prevPlayersOnGround = Sets.newHashSet();
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onMove(PlayerMoveEvent event) {
+
+ if (event.getTo() == null || event.getTo().getWorld() == null) return;
+ checkJumping(event);
+ checkBounds(event);
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onJump(PlayerJumpEvent event) {
+ if(Main.getInstance().getBoard().isSpectator(event.getPlayer()) && event.getPlayer().getAllowFlight()) {
+ event.getPlayer().setFlying(true);
+ }
+ }
+
+ private void checkJumping(PlayerMoveEvent event){
+ if (event.getPlayer().getVelocity().getY() > 0) {
+ if (event.getPlayer().getLocation().getBlock().getType() != Material.LADDER && prevPlayersOnGround.contains(event.getPlayer().getUniqueId())) {
+ if (!event.getPlayer().isOnGround()) {
+ Main.getInstance().getServer().getPluginManager().callEvent(new PlayerJumpEvent(event.getPlayer()));
+ }
+ }
+ }
+ if (event.getPlayer().isOnGround()) {
+ prevPlayersOnGround.add(event.getPlayer().getUniqueId());
+ } else {
+ prevPlayersOnGround.remove(event.getPlayer().getUniqueId());
+ }
+ }
+
+ private void checkBounds(PlayerMoveEvent event){
+ if (!Main.getInstance().getBoard().contains(event.getPlayer())) return;
+ if (!event.getPlayer().getWorld().getName().equals(Main.getInstance().getGame().getCurrentMap().getGameSpawnName())) return;
+ if (!event.getTo().getWorld().getName().equals(Main.getInstance().getGame().getCurrentMap().getGameSpawnName())) return;
+ if (event.getPlayer().hasPermission("hs.leavebounds")) return;
+ Map map = Main.getInstance().getGame().getCurrentMap();
+ if (event.getTo().getBlockX() < map.getBoundsMin().getBlockX() || event.getTo().getBlockX() > map.getBoundsMax().getBlockX() || event.getTo().getBlockZ() < map.getBoundsMin().getZ() || event.getTo().getBlockZ() > map.getBoundsMax().getZ()) {
+ event.setCancelled(true);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/PlayerHandler.java b/src/main/java/dev/tylerm/khs/game/listener/PlayerHandler.java
new file mode 100644
index 0000000..c27400a
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/PlayerHandler.java
@@ -0,0 +1,56 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Items;
+import dev.tylerm.khs.game.util.Status;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.event.entity.FoodLevelChangeEvent;
+import org.bukkit.event.entity.ItemSpawnEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.inventory.ItemStack;
+
+import static dev.tylerm.khs.configuration.Config.dropItems;
+import static dev.tylerm.khs.configuration.Config.regenHealth;
+
+public class PlayerHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onFoodLevelChange(FoodLevelChangeEvent event) {
+ if (event.getEntity() instanceof Player) {
+ if (!Main.getInstance().getBoard().contains((Player) event.getEntity())) return;
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerRegainHealth(EntityRegainHealthEvent event) {
+ if (regenHealth) return;
+ if (event.getRegainReason() == EntityRegainHealthEvent.RegainReason.SATIATED || event.getRegainReason() == EntityRegainHealthEvent.RegainReason.REGEN) {
+ if (event.getEntity() instanceof Player) {
+ if (!Main.getInstance().getBoard().contains((Player) event.getEntity())) return;
+ event.setCancelled(true);
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onItemDrop(PlayerDropItemEvent event) {
+ if (!dropItems && Main.getInstance().getBoard().contains(event.getPlayer())) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onItemSpawn(ItemSpawnEvent event){
+ if(Main.getInstance().getGame().getStatus() == Status.STANDBY) return;
+ ItemStack item = event.getEntity().getItemStack();
+ if (!Items.matchItem(item)) return;
+ if (dropItems) return;
+ event.setCancelled(true);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/RespawnHandler.java b/src/main/java/dev/tylerm/khs/game/listener/RespawnHandler.java
new file mode 100644
index 0000000..0932e4c
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/RespawnHandler.java
@@ -0,0 +1,40 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerRespawnEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class RespawnHandler implements Listener {
+
+ public static final Map<UUID, Location> temp_loc = new HashMap<>();
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ Player player = event.getEntity();
+ if (!Main.getInstance().getBoard().contains(player)) return;
+ event.setKeepInventory(true);
+ event.setDeathMessage("");
+ temp_loc.put(player.getUniqueId(), player.getLocation());
+ Main.getInstance().getLogger().severe("Player " + player.getName() + " died when not supposed to. Attempting to roll back death.");
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerRespawn(PlayerRespawnEvent event) {
+ Player player = event.getPlayer();
+ if (!Main.getInstance().getBoard().contains(player)) return;
+ if (temp_loc.containsKey(player.getUniqueId())) {
+ player.teleport(temp_loc.get(player.getUniqueId()));
+ temp_loc.remove(player.getUniqueId());
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/WorldInteractHandler.java b/src/main/java/dev/tylerm/khs/game/listener/WorldInteractHandler.java
new file mode 100644
index 0000000..97334b2
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/WorldInteractHandler.java
@@ -0,0 +1,48 @@
+package dev.tylerm.khs.game.listener;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.game.Board;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.entity.EntityBreakDoorEvent;
+import org.bukkit.event.hanging.HangingBreakByEntityEvent;
+
+public class WorldInteractHandler implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onBlockBreak(BlockBreakEvent event) {
+ Player player = event.getPlayer();
+ Board board = Main.getInstance().getBoard();
+ if(board.contains(player)) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onHangingEntityBreakByEntity(HangingBreakByEntityEvent event) {
+ if (!(event.getRemover() instanceof Player)) {
+ return;
+ }
+ Player player = (Player) event.getRemover();
+ Board board = Main.getInstance().getBoard();
+ if(board.contains(player)) {
+ event.setCancelled(true);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onBreakDoor(EntityBreakDoorEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+ Player player = (Player) event.getEntity();
+ Board board = Main.getInstance().getBoard();
+ if(board.contains(player)) {
+ event.setCancelled(true);
+ }
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/listener/events/PlayerJumpEvent.java b/src/main/java/dev/tylerm/khs/game/listener/events/PlayerJumpEvent.java
new file mode 100644
index 0000000..24c47b9
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/listener/events/PlayerJumpEvent.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.game.listener.events;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+public class PlayerJumpEvent extends PlayerEvent implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+ private boolean cancel = false;
+
+ public PlayerJumpEvent(Player player) {
+ super(player);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancel;
+ }
+
+ @Override
+ public void setCancelled(boolean b) {
+ this.cancel = !b;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ @SuppressWarnings("unused")
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/util/CountdownDisplay.java b/src/main/java/dev/tylerm/khs/game/util/CountdownDisplay.java
new file mode 100644
index 0000000..5292627
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/util/CountdownDisplay.java
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Kenshins Hide and Seek
+ *
+ * Copyright (c) 2022 Tyler Murphy.
+ *
+ * Kenshins Hide and Seek free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * he Free Software Foundation version 3.
+ *
+ * Kenshins Hide and Seek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package dev.tylerm.khs.game.util;
+
+public enum CountdownDisplay {
+ CHAT,
+ ACTIONBAR,
+ TITLE
+}
diff --git a/src/main/java/dev/tylerm/khs/game/util/Disguise.java b/src/main/java/dev/tylerm/khs/game/util/Disguise.java
new file mode 100644
index 0000000..1bc185e
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/util/Disguise.java
@@ -0,0 +1,219 @@
+package dev.tylerm.khs.game.util;
+
+import com.cryptomorin.xseries.XSound;
+import com.cryptomorin.xseries.messages.ActionBar;
+import dev.tylerm.khs.util.packet.BlockChangePacket;
+import dev.tylerm.khs.util.packet.EntityTeleportPacket;
+import dev.tylerm.khs.Main;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.entity.*;
+import org.bukkit.potion.PotionEffect;
+import org.bukkit.potion.PotionEffectType;
+import org.bukkit.scoreboard.Scoreboard;
+import org.bukkit.scoreboard.Team;
+
+@SuppressWarnings("deprecation")
+public class Disguise {
+
+ final Player hider;
+ final Material material;
+ FallingBlock block;
+ AbstractHorse hitBox;
+ Location blockLocation;
+ boolean solid, solidify, solidifying;
+ static Team hidden;
+
+ static {
+ if(Main.getInstance().supports(9)) {
+ Scoreboard board = Bukkit.getScoreboardManager().getMainScoreboard();
+ hidden = board.getTeam("KHS_Collision");
+ if (hidden == null) {
+ hidden = board.registerNewTeam("KHS_Collision");
+ }
+ hidden.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.NEVER);
+ hidden.setCanSeeFriendlyInvisibles(false);
+ }
+ }
+
+ public Disguise(Player player, Material material){
+ this.hider = player;
+ this.material = material;
+ this.solid = false;
+ respawnFallingBlock();
+ player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 1000000, 0,false, false));
+ if(Main.getInstance().supports(9)) {
+ hidden.addEntry(player.getName());
+ } else {
+ hider.spigot().setCollidesWithEntities(false);
+ }
+ }
+
+ public void remove(){
+ if(block != null)
+ block.remove();
+ if(hitBox != null){
+ if(Main.getInstance().supports(9))
+ hidden.removeEntry(hitBox.getUniqueId().toString());
+ hitBox.remove();
+ }
+ if(solid)
+ sendBlockUpdate(blockLocation, Material.AIR);
+ hider.removePotionEffect(PotionEffectType.INVISIBILITY);
+ if(Main.getInstance().supports(9)) {
+ hidden.removeEntry(hider.getName());
+ } else {
+ hider.spigot().setCollidesWithEntities(true);
+ }
+ }
+
+ public int getEntityID() {
+ if(block == null) return -1;
+ return block.getEntityId();
+ }
+
+ public int getHitBoxID() {
+ if(hitBox == null) return -1;
+ return hitBox.getEntityId();
+ }
+
+ public Player getPlayer() {
+ return hider;
+ }
+
+ public void update(){
+
+ if(block == null || block.isDead()){
+ if(block != null) block.remove();
+ respawnFallingBlock();
+ }
+
+ if(solidify){
+ if(!solid) {
+ solid = true;
+ blockLocation = hider.getLocation().getBlock().getLocation();
+ respawnHitbox();
+ }
+ sendBlockUpdate(blockLocation, material);
+ } else if(solid){
+ solid = false;
+ if(Main.getInstance().supports(9))
+ hidden.removeEntry(hitBox.getUniqueId().toString());
+ hitBox.remove();
+ hitBox = null;
+ sendBlockUpdate(blockLocation, Material.AIR);
+ }
+ toggleEntityVisibility(block, !solid);
+ teleportEntity(hitBox, true);
+ teleportEntity(block, solid);
+ }
+
+ public void setSolidify(boolean value){
+ this.solidify = value;
+ }
+
+ private void sendBlockUpdate(Location location, Material material){
+ BlockChangePacket packet = new BlockChangePacket();
+ packet.setBlockPosition(location);
+ packet.setMaterial(material);
+ Bukkit.getOnlinePlayers().forEach(receiver -> {
+ if(receiver.getName().equals(hider.getName())) return;
+ packet.send(receiver);
+ });
+ }
+
+ private void teleportEntity(Entity entity, boolean center) {
+ if(entity == null) return;
+ EntityTeleportPacket packet = new EntityTeleportPacket();
+ packet.setEntity(entity);
+ double x,y,z;
+ if(center){
+ x = Math.round(hider.getLocation().getX()+.5)-.5;
+ y = Math.round(hider.getLocation().getY());
+ z = Math.round(hider.getLocation().getZ()+.5)-.5;
+ } else {
+ x = hider.getLocation().getX();
+ y = hider.getLocation().getY();
+ z = hider.getLocation().getZ();
+ }
+ packet.setX(x);
+ packet.setY(y);
+ packet.setZ(z);
+ Bukkit.getOnlinePlayers().forEach(packet::send);
+ }
+
+ private void toggleEntityVisibility(Entity entity, boolean show){
+ if(entity == null) return;
+ Bukkit.getOnlinePlayers().forEach(receiver -> {
+ if(receiver == hider) return;
+ if(show)
+ Main.getInstance().getEntityHider().showEntity(receiver, entity);
+ else
+ Main.getInstance().getEntityHider().hideEntity(receiver, entity);
+ });
+ }
+
+ private void respawnFallingBlock(){
+ block = hider.getLocation().getWorld().spawnFallingBlock(hider.getLocation().add(0, 1000, 0), material, (byte)0);
+ if (Main.getInstance().supports(10)) {
+ block.setGravity(false);
+ }
+ block.setDropItem(false);
+ block.setInvulnerable(true);
+ }
+
+ private void respawnHitbox(){
+ if (Main.getInstance().supports(11)) {
+ hitBox = (AbstractHorse) hider.getLocation().getWorld().spawnEntity(hider.getLocation().add(0, 1000, 0), EntityType.SKELETON_HORSE);
+ } else {
+ hitBox = (AbstractHorse) hider.getLocation().getWorld().spawnEntity(hider.getLocation().add(0, 1000, 0), EntityType.HORSE);
+ hitBox.setVariant(Horse.Variant.SKELETON_HORSE);
+ }
+ if (Main.getInstance().supports(10)) {
+ hitBox.setGravity(false);
+ }
+ hitBox.setAI(false);
+ hitBox.setInvulnerable(true);
+ hitBox.setCanPickupItems(false);
+ hitBox.setCollidable(false);
+ hitBox.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, 1000000, 0,false, false));
+ if(Main.getInstance().supports(9)){
+ hidden.addEntry(hitBox.getUniqueId().toString());
+ }
+ }
+
+ public void startSolidifying() {
+ if (solidifying) return;
+ if (solid) return;
+ solidifying = true;
+ final Location lastLocation = hider.getLocation();
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> solidifyUpdate(lastLocation, 3), 10);
+ }
+
+ private void solidifyUpdate(Location lastLocation, int time) {
+ Location currentLocation = hider.getLocation();
+ if(lastLocation.getWorld() != currentLocation.getWorld()) {
+ solidifying = false;
+ return;
+ }
+ if(lastLocation.distance(currentLocation) > .1) {
+ solidifying = false;
+ return;
+ }
+ if(time == 0) {
+ ActionBar.clearActionBar(hider);
+ setSolidify(true);
+ solidifying = false;
+ } else {
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < time; i++) {
+ s.append("▪");
+ }
+ ActionBar.sendActionBar(hider, s.toString());
+ XSound.BLOCK_NOTE_BLOCK_PLING.play(hider, 1, 1);
+ Bukkit.getScheduler().scheduleSyncDelayedTask(Main.getInstance(), () -> solidifyUpdate(lastLocation, time - 1), 20);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/dev/tylerm/khs/game/util/Status.java b/src/main/java/dev/tylerm/khs/game/util/Status.java
new file mode 100644
index 0000000..70035af
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/util/Status.java
@@ -0,0 +1,7 @@
+package dev.tylerm.khs.game.util;
+
+public enum Status {
+
+ STANDBY, STARTING, PLAYING, ENDING, ENDED
+
+}
diff --git a/src/main/java/dev/tylerm/khs/game/util/WinType.java b/src/main/java/dev/tylerm/khs/game/util/WinType.java
new file mode 100644
index 0000000..ad8bfd7
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/game/util/WinType.java
@@ -0,0 +1,7 @@
+package dev.tylerm.khs.game.util;
+
+public enum WinType {
+
+ HIDER_WIN, SEEKER_WIN, NONE
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/Location.java b/src/main/java/dev/tylerm/khs/util/Location.java
new file mode 100644
index 0000000..658b3cc
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/Location.java
@@ -0,0 +1,151 @@
+package dev.tylerm.khs.util;
+
+import dev.tylerm.khs.world.VoidGenerator;
+import dev.tylerm.khs.Main;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.WorldCreator;
+import org.bukkit.WorldType;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+import static dev.tylerm.khs.configuration.Config.spawnPatch;
+
+public class Location {
+
+ private final String world;
+ private final double x;
+ private final double y;
+ private final double z;
+
+ public static Location getDefault() {
+ return new Location(
+ "",
+ 0.0,
+ 0.0,
+ 0.0
+ );
+ }
+
+ public static Location from(Player player) {
+ org.bukkit.Location location = player.getLocation();
+ return new Location(
+ player.getWorld().getName(),
+ location.getX(),
+ location.getY(),
+ location.getZ()
+ );
+ }
+
+ public Location(@NotNull String world, double x, double y, double z) {
+ this.world = world;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public Location(@NotNull String world, @NotNull org.bukkit.Location location) {
+ this.world = world;
+ this.x = location.getX();
+ this.y = location.getY();
+ this.z = location.getZ();
+ }
+
+ public World load(WorldType type, World.Environment environment) {
+ boolean mapSave = world.startsWith("hs_");
+ World bukkitWorld = Bukkit.getWorld(world);
+ if(bukkitWorld != null) return bukkitWorld;
+ WorldCreator creator = new WorldCreator(world);
+ if(type != null) {
+ creator.type(type);
+ }
+ if(environment != null) {
+ creator.environment(environment);
+ }
+ if(mapSave) {
+ creator.generator(new VoidGenerator());
+ }
+ Bukkit.getServer().createWorld(creator).setAutoSave(!mapSave);
+ return Bukkit.getWorld(world);
+ }
+
+ public World load() {
+ if(!exists()) return null;
+ if(!Main.getInstance().isLoaded()) return null;
+ return load(null, null);
+ }
+
+ private org.bukkit.Location toBukkit() {
+ return new org.bukkit.Location(
+ Bukkit.getWorld(world),
+ x,
+ y,
+ z
+ );
+ }
+
+ public void teleport(Player player) {
+ if(!exists()) return;
+ if(load() == null) return;
+ if (spawnPatch) {
+ Main.getInstance().scheduleTask(() -> player.teleport(toBukkit()));
+ } else {
+ player.teleport(toBukkit());
+ }
+ }
+
+ public Location changeWorld(String world) {
+ return new Location(
+ world,
+ x,
+ y,
+ z
+ );
+ }
+
+ public String getWorld() {
+ return world;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+
+ public int getBlockX() {
+ return (int)x;
+ }
+
+ public int getBlockY() {
+ return (int)y;
+ }
+
+ public int getBlockZ() {
+ return (int)z;
+ }
+
+ public boolean exists() {
+ if(world.equals("")) return false;
+ String path = Main.getInstance().getWorldContainer()+File.separator+world;
+ File destination = new File(path);
+ return destination.isDirectory();
+ }
+
+ public boolean isNotSetup() {
+ return getBlockX() == 0 && getBlockY() == 0 && getBlockZ() == 0;
+ }
+
+ public boolean isNotInBounds(int xmin, int xmax, int zmin, int zmax) {
+ return getBlockX() <= xmin || getBlockX() >= xmax || getBlockZ() <= zmin || getBlockZ() >= zmax;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/PAPIExpansion.java b/src/main/java/dev/tylerm/khs/util/PAPIExpansion.java
new file mode 100644
index 0000000..e29a098
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/PAPIExpansion.java
@@ -0,0 +1,174 @@
+package dev.tylerm.khs.util;
+
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.database.Database;
+import dev.tylerm.khs.database.util.PlayerInfo;
+import dev.tylerm.khs.game.Board;
+import dev.tylerm.khs.game.util.Status;
+
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static dev.tylerm.khs.configuration.Config.placeholderError;
+import static dev.tylerm.khs.configuration.Config.placeholderNoData;
+
+public class PAPIExpansion extends PlaceholderExpansion {
+
+ @Override
+ public @NotNull String getIdentifier() {
+ return "hs";
+ }
+
+ @Override
+ public @NotNull String getAuthor() {
+ return "KenshinEto";
+ }
+
+ @Override
+ public @NotNull String getVersion() {
+ return "1.7.5";
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public String onRequest(OfflinePlayer player, @NotNull String params) {
+ Database database = Main.getInstance().getDatabase();
+ String[] args = params.split("_");
+ Status status = Main.getInstance().getGame().getStatus();
+ Board board = Main.getInstance().getBoard();
+
+ System.out.println(args);
+
+ if (args.length < 1) return null;
+
+ if (args.length == 1 && args[0].equals("hiders")) {
+ if (!board.containsUUID(player.getUniqueId())) {
+ return "-";
+ } else if (status == Status.PLAYING || status == Status.STARTING) {
+ return "" + Main.getInstance().getBoard().getHiders().size();
+ } else {
+ return "-";
+ }
+ }
+
+ if (args.length == 1 && args[0].equals("seekers")) {
+ if (!board.containsUUID(player.getUniqueId())) {
+ return "-";
+ } else if (status == Status.PLAYING || status == Status.STARTING) {
+ return "" + Main.getInstance().getBoard().getSeekers().size();
+ } else {
+ return "-";
+ }
+ }
+
+ if ((args.length == 2 || args.length == 3) && (args[0].equals("stats") || args[0].equals("rank-place"))) {
+ Optional<PlayerInfo> info = this.getPlayerInfo(args.length == 2 ? player.getUniqueId() : database.getNameData().getUUID(args[2]));
+ if (info.isPresent()) {
+ switch (args[0]) {
+ case "stats":
+ return getValue(info.get(), args[1]);
+ case "rank-place":
+ if (getRanking(args[1]) == null) return placeholderError;
+ Integer count = database.getGameData().getRanking(getRanking(args[1]), player.getUniqueId());
+ if (getValue(info.get(), args[1]).equals("0")) return "-";
+ if (count == null) return placeholderNoData;
+ return count.toString();
+ }
+ } else switch (args[0]) {
+ case "stats":
+ return placeholderNoData;
+ case "rank-place":
+ return "-";
+ }
+ }
+
+ if ((args[0].equals("rank-score") || args[0].equals("rank-name")) && args.length == 3) {
+ int place = Integer.parseInt(args[2]);
+ if (place < 1 || getRanking(args[1]) == null) return placeholderError;
+
+ PlayerInfo info = database.getGameData().getInfoRanking(getRanking(args[1]), place);
+ if (info == null) return placeholderNoData;
+
+ return args[0].equals("rank-score") ? getValue(info, args[1]) : Main.getInstance().getServer().getOfflinePlayer(info.getUniqueId()).getName();
+ }
+ return null;
+ }
+
+ private String getValue(PlayerInfo info, String query) {
+ if (query == null) return null;
+ switch (query) {
+ case "total-wins":
+ return String.valueOf(info.getHiderWins() + info.getSeekerWins());
+ case "hider-wins":
+ return String.valueOf(info.getHiderWins());
+ case "seeker-wins":
+ return String.valueOf(info.getSeekerWins());
+ case "total-games":
+ return String.valueOf(info.getHiderGames() + info.getSeekerGames());
+ case "hider-games":
+ return String.valueOf(info.getHiderGames());
+ case "seeker-games":
+ return String.valueOf(info.getSeekerGames());
+ case "total-kills":
+ return String.valueOf(info.getHiderKills() + info.getSeekerKills());
+ case "hider-kills":
+ return String.valueOf(info.getHiderKills());
+ case "seeker-kills":
+ return String.valueOf(info.getSeekerKills());
+ case "total-deaths":
+ return String.valueOf(info.getHiderDeaths() + info.getSeekerDeaths());
+ case "hider-deaths":
+ return String.valueOf(info.getHiderDeaths());
+ case "seeker-deaths":
+ return String.valueOf(info.getSeekerDeaths());
+ default:
+ return null;
+ }
+ }
+
+ private String getRanking(@NotNull String query) {
+ switch (query) {
+ case "total-wins":
+ return "(hider_wins + seeker_wins)";
+ case "hider-wins":
+ return "hider_wins";
+ case "seeker-wins":
+ return "seeker_wins";
+ case "total-games":
+ return "(hider_games + seeker_games)";
+ case "hider-games":
+ return "hider_games";
+ case "seeker-games":
+ return "seeker_games";
+ case "total-kills":
+ return "(hider_kills + seeker_kills)";
+ case "hider-kills":
+ return "hider_kills";
+ case "seeker-kills":
+ return "seeker_kills";
+ case "total-deaths":
+ return "(hider_deaths + seeker_deaths)";
+ case "hider-deaths":
+ return "hider_deaths";
+ case "seeker-deaths":
+ return "seeker_deaths";
+ default:
+ return null;
+ }
+ }
+
+ private Optional<PlayerInfo> getPlayerInfo(@Nullable UUID uniqueId) {
+ return Optional.ofNullable(Main.getInstance().getDatabase().getGameData().getInfo(uniqueId));
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/Pair.java b/src/main/java/dev/tylerm/khs/util/Pair.java
new file mode 100644
index 0000000..a650e76
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/Pair.java
@@ -0,0 +1,21 @@
+package dev.tylerm.khs.util;
+
+public class Pair<L, R> {
+
+ private final L left;
+ private final R right;
+
+ public Pair(L left, R right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ public L getLeft() {
+ return left;
+ }
+
+ public R getRight() {
+ return right;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/Tuple.java b/src/main/java/dev/tylerm/khs/util/Tuple.java
new file mode 100644
index 0000000..64ebda6
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/Tuple.java
@@ -0,0 +1,27 @@
+package dev.tylerm.khs.util;
+
+public class Tuple<L, C, R> {
+
+ private final L left;
+ private final C center;
+ private final R right;
+
+ public Tuple(L left, C center, R right) {
+ this.left = left;
+ this.center = center;
+ this.right = right;
+ }
+
+ public L getLeft() {
+ return left;
+ }
+
+ public C getCenter() {
+ return center;
+ }
+
+ public R getRight() {
+ return right;
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/packet/AbstractPacket.java b/src/main/java/dev/tylerm/khs/util/packet/AbstractPacket.java
new file mode 100644
index 0000000..e6113ac
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/packet/AbstractPacket.java
@@ -0,0 +1,29 @@
+package dev.tylerm.khs.util.packet;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketContainer;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class AbstractPacket {
+
+ private static final ProtocolManager protocolManager;
+ static {
+ protocolManager = ProtocolLibrary.getProtocolManager();
+ }
+
+ protected final PacketContainer packet;
+
+ protected AbstractPacket(PacketType type){
+ packet = protocolManager.createPacket(type);
+ packet.getModifier().writeDefaults();
+ }
+
+ public void send(Player player){
+ protocolManager.sendServerPacket(player, packet);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/packet/BlockChangePacket.java b/src/main/java/dev/tylerm/khs/util/packet/BlockChangePacket.java
new file mode 100644
index 0000000..0603832
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/packet/BlockChangePacket.java
@@ -0,0 +1,24 @@
+package dev.tylerm.khs.util.packet;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.wrappers.BlockPosition;
+import com.comphenix.protocol.wrappers.WrappedBlockData;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockChangePacket extends AbstractPacket {
+
+ public BlockChangePacket(){
+ super(PacketType.Play.Server.BLOCK_CHANGE);
+ }
+
+ public void setBlockPosition(@NotNull Location location){
+ super.packet.getBlockPositionModifier().write(0, new BlockPosition(location.toVector()));
+ }
+
+ public void setMaterial(Material material){
+ super.packet.getBlockData().write(0, WrappedBlockData.createData(material));
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/packet/EntityMetadataPacket.java b/src/main/java/dev/tylerm/khs/util/packet/EntityMetadataPacket.java
new file mode 100644
index 0000000..cf5d2a1
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/packet/EntityMetadataPacket.java
@@ -0,0 +1,69 @@
+package dev.tylerm.khs.util.packet;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.wrappers.WrappedDataValue;
+import com.comphenix.protocol.wrappers.WrappedDataWatcher;
+import com.comphenix.protocol.wrappers.WrappedWatchableObject;
+
+import dev.tylerm.khs.Main;
+
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EntityMetadataPacket extends AbstractPacket {
+
+ private final WrappedDataWatcher watcher;
+ private final WrappedDataWatcher.Serializer serializer;
+
+ public EntityMetadataPacket(){
+ super(PacketType.Play.Server.ENTITY_METADATA);
+ watcher = new WrappedDataWatcher();
+ serializer = WrappedDataWatcher.Registry.get(Byte.class);
+ }
+
+ public void setEntity(@NotNull Entity target){
+ super.packet.getIntegers().write(0, target.getEntityId());
+ watcher.setEntity(target);
+ }
+
+ public void setGlow(boolean glowing){
+ if (glowing) {
+ watcher.setObject(0, serializer, (byte) (0x40));
+ } else {
+ watcher.setObject(0, serializer, (byte) (0x0));
+ }
+ }
+
+ public void writeMetadata() {
+
+ if (Main.getInstance().supports(19, 3)) {
+
+ final List<WrappedDataValue> wrappedDataValueList = new ArrayList<>();
+
+ for(final WrappedWatchableObject entry : watcher.getWatchableObjects()) {
+ if(entry == null) continue;
+
+ final WrappedDataWatcher.WrappedDataWatcherObject watcherObject = entry.getWatcherObject();
+ wrappedDataValueList.add(
+ new WrappedDataValue(
+ watcherObject.getIndex(),
+ watcherObject.getSerializer(),
+ entry.getRawValue()
+ )
+ );
+ }
+
+ packet.getDataValueCollectionModifier().write(0, wrappedDataValueList);
+
+ } else {
+
+ packet.getWatchableCollectionModifier().write(0, watcher.getWatchableObjects());
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/util/packet/EntityTeleportPacket.java b/src/main/java/dev/tylerm/khs/util/packet/EntityTeleportPacket.java
new file mode 100644
index 0000000..cd29e28
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/util/packet/EntityTeleportPacket.java
@@ -0,0 +1,29 @@
+package dev.tylerm.khs.util.packet;
+
+import com.comphenix.protocol.PacketType;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+public class EntityTeleportPacket extends AbstractPacket {
+
+ public EntityTeleportPacket(){
+ super(PacketType.Play.Server.ENTITY_TELEPORT);
+ }
+
+ public void setEntity(@NotNull Entity entity){
+ super.packet.getIntegers().write(0, entity.getEntityId());
+ }
+
+ public void setX(double x){
+ super.packet.getDoubles().write(0, x);
+ }
+
+ public void setY(double y){
+ super.packet.getDoubles().write(1, y);
+ }
+
+ public void setZ(double z){
+ super.packet.getDoubles().write(2, z);
+ }
+
+}
diff --git a/src/main/java/dev/tylerm/khs/world/VoidGenerator.java b/src/main/java/dev/tylerm/khs/world/VoidGenerator.java
new file mode 100644
index 0000000..eb972e8
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/world/VoidGenerator.java
@@ -0,0 +1,55 @@
+package dev.tylerm.khs.world;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.block.Biome;
+import org.bukkit.generator.BlockPopulator;
+import org.bukkit.generator.ChunkGenerator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+@SuppressWarnings({"unused"})
+public class VoidGenerator extends ChunkGenerator {
+
+ // 1.14 And On
+ public @NotNull List<BlockPopulator> getDefaultPopulators(@NotNull World world) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean canSpawn(World world, int x, int z) {
+ return true;
+ }
+
+ @Override
+ public Location getFixedSpawnLocation(World world, Random random) {
+ return new Location(world, 0, 100, 0);
+ }
+
+ // 1.13 And Prev
+ public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome) {
+ ChunkData chunkData = super.createChunkData(world);
+
+ for(int x = 0; x < 16; x++) {
+ for(int z = 0; z < 16; z++) {
+ biome.setBiome(x, z, Biome.PLAINS);
+ }
+ }
+
+ return chunkData;
+ }
+
+ // 1.8
+ public byte[] generate(World world, Random random, int x, int z) {
+ return new byte[world.getMaxHeight() / 16];
+ }
+
+ public byte[][] generateBlockSections(World world, Random random, int x, int z, ChunkGenerator.BiomeGrid biomes) {
+ return new byte[world.getMaxHeight() / 16][];
+ }
+
+
+}
diff --git a/src/main/java/dev/tylerm/khs/world/WorldLoader.java b/src/main/java/dev/tylerm/khs/world/WorldLoader.java
new file mode 100644
index 0000000..6f13548
--- /dev/null
+++ b/src/main/java/dev/tylerm/khs/world/WorldLoader.java
@@ -0,0 +1,155 @@
+package dev.tylerm.khs.world;
+
+import dev.tylerm.khs.Main;
+import dev.tylerm.khs.configuration.Map;
+import org.bukkit.Bukkit;
+import org.bukkit.GameRule;
+import org.bukkit.World;
+import org.bukkit.WorldCreator;
+
+import java.io.*;
+import java.nio.file.Files;
+
+import static dev.tylerm.khs.configuration.Config.*;
+import static dev.tylerm.khs.configuration.Localization.message;
+
+public class WorldLoader {
+
+ private final Map map;
+
+ public WorldLoader(Map map) {
+ this.map = map;
+ }
+
+ public World getWorld() {
+ return Bukkit.getServer().getWorld(map.getGameSpawnName());
+ }
+
+ public void unloadMap() {
+ World world = Bukkit.getServer().getWorld(map.getGameSpawnName());
+ if (world == null) {
+ Main.getInstance().getLogger().warning(map.getGameSpawnName() + " already unloaded.");
+ return;
+ }
+ world.getPlayers().forEach(player -> exitPosition.teleport(player));
+ Main.getInstance().scheduleTask(() -> {
+ if (Bukkit.getServer().unloadWorld(world, false)) {
+ Main.getInstance().getLogger().info("Successfully unloaded " + map.getGameSpawnName());
+ } else {
+ Main.getInstance().getLogger().severe("COULD NOT UNLOAD " + map.getGameSpawnName());
+ }
+ });
+ }
+
+ public void loadMap() {
+ Main.getInstance().scheduleTask(() -> {
+ Bukkit.getServer().createWorld(new WorldCreator(map.getGameSpawnName()).generator(new VoidGenerator()));
+ World world = Bukkit.getServer().getWorld(map.getGameSpawnName());
+ if (world == null) {
+ Main.getInstance().getLogger().severe("COULD NOT LOAD " + map.getGameSpawnName());
+ return;
+ }
+ world.setAutoSave(false);
+ });
+ }
+
+ public void rollback() {
+ unloadMap();
+ loadMap();
+ }
+
+ public String save() {
+ World world = Bukkit.getServer().getWorld(map.getSpawnName());
+ if(world == null){
+ return errorPrefix + message("MAPSAVE_INVALID").addAmount(map.getSpawnName());
+ }
+ File current = new File(Main.getInstance().getWorldContainer()+File.separator+ map.getSpawnName());
+ if (current.exists()) {
+ try {
+ File destination = new File(Main.getInstance().getWorldContainer()+File.separator+ map.getGameSpawnName());
+ File temp_destination = new File(Main.getInstance().getWorldContainer()+File.separator+"temp_"+ map.getGameSpawnName());
+ copyFileFolder("region",true);
+ copyFileFolder("entities",true);
+ copyFileFolder("datapacks",false);
+ copyFileFolder("data",false);
+ File srcFile = new File(current, "level.dat");
+ File destFile = new File(temp_destination, "level.dat");
+ copyFile(srcFile,destFile);
+ if (destination.exists()) {
+ deleteDirectory(destination);
+ }
+
+ if (!temp_destination.renameTo(destination)) {
+ return errorPrefix + message("MAPSAVE_FAIL_DIR").addAmount(temp_destination.getPath());
+ }
+ } catch(IOException e) {
+ e.printStackTrace();
+ return errorPrefix + message("COMMAND_ERROR");
+ }
+ return messagePrefix + message("MAPSAVE_END");
+ } else {
+ return errorPrefix + message("MAPSAVE_ERROR");
+ }
+ }
+
+ private void copyFileFolder(String name, Boolean isMca) throws IOException {
+ File region = new File(Main.getInstance().getWorldContainer()+File.separator+ map.getSpawnName() +File.separator+name);
+ File temp = new File(Main.getInstance().getWorldContainer()+File.separator+"temp_"+ map.getGameSpawnName() +File.separator+name);
+ if (region.exists() && region.isDirectory()) {
+ if (!temp.exists())
+ if (!temp.mkdirs())
+ throw new IOException("Couldn't create region directory!");
+ String[] files = region.list();
+ if (files == null) {
+ Main.getInstance().getLogger().severe("Region directory is null or cannot be accessed");
+ return;
+ }
+ for (String file : files) {
+ if (isMca) {
+ int minX = (int)Math.floor(map.getBoundsMin().getX() / 512.0);
+ int minZ = (int)Math.floor(map.getBoundsMin().getZ() / 512.0);
+ int maxX = (int)Math.floor(map.getBoundsMax().getX() / 512.0);
+ int maxZ = (int)Math.floor(map.getBoundsMax().getZ() / 512.0);
+
+ String[] parts = file.split("\\.");
+ if (parts.length > 1) {
+ if ( Integer.parseInt(parts[1]) < minX || Integer.parseInt(parts[1]) > maxX || Integer.parseInt(parts[2]) < minZ || Integer.parseInt(parts[2]) > maxZ )
+ continue;
+ }
+ }
+
+ File srcFile = new File(region, file);
+ if (srcFile.isDirectory()) {
+ copyFileFolder(name+File.separator+file, false);
+ } else {
+ File destFile = new File(temp, file);
+ copyFile(srcFile, destFile);
+ }
+ }
+ }
+ }
+
+ private void copyFile(File source, File target) throws IOException {
+ InputStream in = Files.newInputStream(source.toPath());
+ OutputStream out = Files.newOutputStream(target.toPath());
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = in.read(buffer)) > 0)
+ out.write(buffer, 0, length);
+ in.close();
+ out.close();
+ }
+
+ public static void deleteDirectory(File directoryToBeDeleted) {
+ File[] allContents = directoryToBeDeleted.listFiles();
+ if (allContents != null) {
+ for (File file : allContents) {
+ deleteDirectory(file);
+ }
+ }
+ if (!directoryToBeDeleted.delete()) {
+ throw new RuntimeException("Failed to delete directory: "+directoryToBeDeleted.getPath());
+ }
+ }
+
+}