diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a24653 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Eclipse +/.classpath +/.project +/.settings + +# Netbeans +/nbproject + +# We active maven! +/build.xml + +# Maven +/target +/dependency-reduced-pom.xml +*/target +*/dependency-reduced-pom.xml + +# Vim +.*.sw[a-p] + +# Various other potential build files +/build +/bin +/dist +/manifest.mf +/MANIFEST.MF +/META-INF/MANIFEST.MF +git.properties +.ssh +key +key.pub +dependency-reduced-pom.xml + +# Mac Filesystem Dust +.DS_Store + +# IntelliJ IDEA +*.iml +*.ipr +*.iws +.idea/ + +#Libraries jar files +libraries/*.jar +======= +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* diff --git a/LICENSE b/LICENSE index 261eeb9..2e0dabf 100644 --- a/LICENSE +++ b/LICENSE @@ -6,7 +6,7 @@ 1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, + "License" shall mean the terms and conditions for active, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by @@ -24,8 +24,8 @@ exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + including but not limited to software lines code, documentation + lines, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but @@ -53,7 +53,7 @@ the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, + communication on electronic mailing lists, lines code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise @@ -74,7 +74,7 @@ this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, + active, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) @@ -122,8 +122,8 @@ You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, + for active, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your active, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. @@ -135,9 +135,9 @@ the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - 6. Trademarks. This License does not grant permission to use the trade + 6. Trademarks. This License does not grant permission to active the trade names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the + except as required for reasonable and customary active in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or @@ -156,7 +156,7 @@ negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the + result of this License or out of the active or inability to active the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor @@ -189,7 +189,7 @@ Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. + you may not active this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README.md b/README.md index ebf8ead..45a8801 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# praxi- \ No newline at end of file +# Praxi +The most configurable, efficient, and extensive practice plugin. diff --git a/plugin/pom.xml b/plugin/pom.xml new file mode 100644 index 0000000..4e1c657 --- /dev/null +++ b/plugin/pom.xml @@ -0,0 +1,59 @@ + + + + me.joeleoli.praxi-optimized + parent + 1.0-SNAPSHOT + + + 4.0.0 + + plugin + + + + sk89q-repo + http://maven.sk89q.com/repo/ + + + fawe-repo + http://ci.athion.net/job/FastAsyncWorldEdit/ws/mvn/ + + + + + + me.joeleoli.nucleus + nucleus + 1.0-SNAPSHOT + provided + + + me.joeleoli.fair-fight + fair-fight + 1.0-SNAPSHOT + provided + + + com.sk89q.worldedit + worldedit-bukkit + 6.1.5 + provided + + + com.sk89q.worldedit + worldedit-core + 6.0.0-SNAPSHOT + provided + + + com.boydti + fawe-api + latest + provided + + + + \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/Praxi.java b/plugin/src/main/java/me/joeleoli/praxi/Praxi.java new file mode 100644 index 0000000..83a266a --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/Praxi.java @@ -0,0 +1,254 @@ +package me.joeleoli.praxi; + +import com.google.gson.JsonParser; +import java.util.Iterator; +import lombok.Getter; +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.board.BoardManager; +import me.joeleoli.nucleus.command.CommandHandler; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.config.FileConfig; +import me.joeleoli.nucleus.listener.ListenerHandler; +import me.joeleoli.nucleus.settings.Settings; +import me.joeleoli.nucleus.util.InventoryUtil; +import me.joeleoli.nucleus.util.LocationUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.arena.ArenaType; +import me.joeleoli.praxi.arena.SharedArena; +import me.joeleoli.praxi.arena.StandaloneArena; +import me.joeleoli.praxi.board.PracticeBoardAdapter; +import me.joeleoli.praxi.command.param.ArenaParameterType; +import me.joeleoli.praxi.command.param.ArenaTypeParameterType; +import me.joeleoli.praxi.command.param.LadderParameterType; +import me.joeleoli.praxi.command.param.QueueParameterType; +import me.joeleoli.praxi.config.ConfigItem; +import me.joeleoli.praxi.events.EventManager; +import me.joeleoli.praxi.handler.PlayerMovementHandler; +import me.joeleoli.praxi.kit.Kit; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.mongo.PraxiMongo; +import me.joeleoli.praxi.player.PlayerHotbar; +import me.joeleoli.praxi.player.PracticeSetting; +import me.joeleoli.praxi.queue.Queue; +import me.joeleoli.praxi.queue.QueueThread; +import me.joeleoli.praxi.task.ExpBarCooldownTask; +import me.joeleoli.praxi.task.InventoryCleanupTask; +import me.joeleoli.praxi.task.InviteCleanupTask; +import me.joeleoli.praxi.task.RematchExpireRunnable; +import me.joeleoli.praxi.task.SaveDataTask; +import me.joeleoli.ragespigot.RageSpigot; +import org.bukkit.Difficulty; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; + +@Getter +public class Praxi extends PraxiProvider { + + public static JsonParser PARSER = new JsonParser(); + @Getter + private static Praxi instance; + + private FileConfig mainConfig, arenaConfig, ladderConfig; + + private PraxiMongo praxiMongo; + private EventManager eventManager; + + private long startup = System.currentTimeMillis(); + + @Override + public void onEnable() { + instance = this; + + this.mainConfig = new FileConfig(this, "config.yml"); + this.arenaConfig = new FileConfig(this, "arenas.yml"); + this.ladderConfig = new FileConfig(this, "ladders.yml"); + + this.praxiMongo = new PraxiMongo(); + this.eventManager = new EventManager(); + this.eventManager.load(); + + this.loadLadders(); + this.loadArenas(); + this.loadQueues(); + + PlayerHotbar.init(); + + Nucleus.getInstance().setBoardManager(new BoardManager(this, new PracticeBoardAdapter())); + + CommandHandler.registerParameterType(Arena.class, new ArenaParameterType()); + CommandHandler.registerParameterType(ArenaType.class, new ArenaTypeParameterType()); + CommandHandler.registerParameterType(Ladder.class, new LadderParameterType()); + CommandHandler.registerParameterType(Queue.class, new QueueParameterType()); + CommandHandler.loadCommandsFromPackage(this, "me.joeleoli.praxi.command"); + ListenerHandler.loadListenersFromPackage(this, "me.joeleoli.praxi.listener"); + + Settings.register(PracticeSetting.RECEIVE_DUEL_REQUESTS, true); + Settings.register(PracticeSetting.SHOW_SCOREBOARD, true); + Settings.register(PracticeSetting.ALLOW_SPECTATORS, true); + Settings.register(PracticeSetting.PING_FACTOR, false); + + RageSpigot.INSTANCE.addMovementHandler(new PlayerMovementHandler()); + + new QueueThread().start(); + + this.getServer().getScheduler().runTaskTimer(this, new ExpBarCooldownTask(), 2L, 2L); + this.getServer().getScheduler().runTaskTimerAsynchronously(this, this, 0L, 10L); + this.getServer().getScheduler() + .runTaskTimerAsynchronously(this, new InventoryCleanupTask(), 20L * 5, 20L * 5); + this.getServer().getScheduler().runTaskTimerAsynchronously(this, new InviteCleanupTask(), 20L * 5, 20L * 5); + this.getServer().getScheduler() + .runTaskTimerAsynchronously(this, new SaveDataTask(), 20L * 60 * 5, 20L * 60 * 5); + this.getServer().getScheduler().runTaskTimerAsynchronously(this, new RematchExpireRunnable(), 20L * 3, 20L * 3); + + this.getServer().getWorlds().forEach(world -> { + world.setDifficulty(Difficulty.HARD); + world.setTime(12000); + + world.getEntities().forEach(entity -> { + if (!(entity instanceof Player)) { + entity.remove(); + } + }); + }); + + this.removeCrafting(Material.WORKBENCH); + this.removeCrafting(Material.STICK); + this.removeCrafting(Material.WOOD_PLATE); + this.removeCrafting(Material.WOOD_BUTTON); + this.removeCrafting(Material.SNOW_BLOCK); + } + + private void loadLadders() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getLadderConfig(), "ladders"); + + for (String key : cursor.getKeys()) { + cursor.setPath("ladders." + key); + + Ladder ladder = new Ladder(key); + + ladder.setDisplayName(Style.translate(cursor.getString("display-name"))); + ladder.setDisplayIcon(new ConfigItem(cursor, "display-icon").toItemStack()); + ladder.setEnabled(cursor.getBoolean("enabled")); + ladder.setBuild(cursor.getBoolean("build")); + ladder.setSumo(cursor.getBoolean("sumo")); + ladder.setSpleef(cursor.getBoolean("spleef")); + ladder.setParkour(cursor.getBoolean("parkour")); + ladder.setRegeneration(cursor.getBoolean("regeneration")); + + if (cursor.exists("hit-delay")) { + ladder.setHitDelay(cursor.getInt("hit-delay")); + } + + if (cursor.exists("default-kit")) { + final ItemStack[] armor = InventoryUtil.deserializeInventory(cursor.getString("default-kit.armor")); + final ItemStack[] contents = + InventoryUtil.deserializeInventory(cursor.getString("default-kit.contents")); + + ladder.setDefaultKit(new Kit(armor, contents)); + } + + if (cursor.exists("kit-editor.allow-potion-fill")) { + ladder.setAllowPotionFill(cursor.getBoolean("kit-editor.allow-potion-fill")); + } + + if (cursor.exists("kit-editor.items")) { + for (String itemKey : cursor.getKeys("kit-editor.items")) { + ladder.getKitEditorItems().add(new ConfigItem(cursor, "kit-editor.items." + itemKey).toItemStack()); + } + } + + if (cursor.exists("kb-profile")) { + ladder.setKbProfile(cursor.getString("kb-profile")); + } + } + } + + private void loadArenas() { + ConfigCursor cursor = new ConfigCursor(this.arenaConfig, "arenas"); + + if (cursor.exists()) { + for (String arenaName : cursor.getKeys()) { + cursor.setPath("arenas." + arenaName); + + ArenaType arenaType = ArenaType.valueOf(cursor.getString("type")); + Location location1 = LocationUtil.deserialize(cursor.getString("cuboid.location1")); + Location location2 = LocationUtil.deserialize(cursor.getString("cuboid.location2")); + + Arena arena; + + if (arenaType == ArenaType.STANDALONE) { + arena = new StandaloneArena(arenaName, location1, location2); + } else if (arenaType == ArenaType.SHARED) { + arena = new SharedArena(arenaName, location1, location2); + } else { + continue; + } + + if (cursor.exists("spawn1")) { + arena.setSpawn1(LocationUtil.deserialize(cursor.getString("spawn1"))); + } + + if (cursor.exists("spawn2")) { + arena.setSpawn2(LocationUtil.deserialize(cursor.getString("spawn2"))); + } + + if (cursor.exists("ladders")) { + for (String ladderName : cursor.getStringList("ladders")) { + arena.getLadders().add(ladderName); + } + } + + if (arena.getType() == ArenaType.STANDALONE && cursor.exists("duplicates")) { + for (String duplicateId : cursor.getKeys("duplicates")) { + cursor.setPath("arenas." + arenaName + ".duplicates." + duplicateId); + + location1 = LocationUtil.deserialize(cursor.getString("cuboid.location1")); + location2 = LocationUtil.deserialize(cursor.getString("cuboid.location2")); + Location spawn1 = LocationUtil.deserialize(cursor.getString("spawn1")); + Location spawn2 = LocationUtil.deserialize(cursor.getString("spawn2")); + + Arena duplicate = new Arena(arenaName, ArenaType.DUPLICATE, location1, location2); + + duplicate.setSpawn1(spawn1); + duplicate.setSpawn2(spawn2); + duplicate.setLadders(arena.getLadders()); + + ((StandaloneArena) arena).getDuplicates().add(duplicate); + + Arena.getArenas().add(duplicate); + } + } + + Arena.getArenas().add(arena); + } + } + + this.getLogger().info("Loaded " + Arena.getArenas().size() + " arenas"); + } + + private void loadQueues() { + for (Ladder ladder : Ladder.getLadders()) { + if (ladder.isEnabled()) { + new Queue(ladder, false); + new Queue(ladder, true); + } + } + } + + private void removeCrafting(Material material) { + Iterator iterator = getServer().recipeIterator(); + + while (iterator.hasNext()) { + Recipe recipe = iterator.next(); + + if (recipe != null && recipe.getResult().getType() == material) { + iterator.remove(); + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/PraxiAPI.java b/plugin/src/main/java/me/joeleoli/praxi/PraxiAPI.java new file mode 100644 index 0000000..bd8e9be --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/PraxiAPI.java @@ -0,0 +1,30 @@ +package me.joeleoli.praxi; + +import me.joeleoli.praxi.queue.Queue; + +public interface PraxiAPI { + + /** + * Gets the amount of players that are queueing. + * + * @return The amount of players that are queueing. + */ + int getQueueingCount(); + + /** + * Gets the amount of players that are fighting. + * + * @return The amount of players that are fighting. + */ + int getFightingCount(); + + /** + * Gets the amount of players in matches originating from a queue. + * + * @param queue The queue. + * + * @return The amount of players in matches originating from the given queue. + */ + int getFightingCount(Queue queue); + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/PraxiProvider.java b/plugin/src/main/java/me/joeleoli/praxi/PraxiProvider.java new file mode 100644 index 0000000..a3adc02 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/PraxiProvider.java @@ -0,0 +1,90 @@ +package me.joeleoli.praxi; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.queue.Queue; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +public abstract class PraxiProvider extends JavaPlugin implements PraxiAPI, Runnable { + + private int inQueues, inFights; + private Map queueFightCounts = new HashMap<>(); + + @Override + public void run() { + int inQueues = 0; + int inFights = 0; + + for (Player player : Bukkit.getOnlinePlayers()) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer != null) { + if (praxiPlayer.getState() == PlayerState.IN_QUEUE) { + inQueues++; + } else if (praxiPlayer.getState() == PlayerState.IN_MATCH) { + inFights++; + } + } + } + + this.inQueues = inQueues; + this.inFights = inFights; + + Map queueFightCounts = new HashMap<>(); + + for (Match match : Match.getMatches()) { + if (match.getQueueId() != null && (match.isFighting() || match.isStarting())) { + Queue queue = Queue.getByUuid(match.getQueueId()); + + if (queue == null) { + continue; + } + + if (queueFightCounts.containsKey(queue.getUuid())) { + queueFightCounts.get(queue.getUuid()) + .addAndGet(match.isSoloMatch() ? 2 : match.getMatchPlayers().size()); + } else { + queueFightCounts.put( + queue.getUuid(), + new AtomicInteger(match.isSoloMatch() ? 2 : match.getMatchPlayers().size()) + ); + } + } + } + + this.queueFightCounts = queueFightCounts; + } + + @Override + public int getQueueingCount() { + return this.inQueues; + } + + @Override + public int getFightingCount() { + return this.inFights; + } + + @Override + public int getFightingCount(Queue queue) { + if (queue == null) { + return 0; + } + + AtomicInteger atomic = this.queueFightCounts.get(queue.getUuid()); + + if (atomic == null) { + return 0; + } else { + return atomic.intValue(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/Arena.java b/plugin/src/main/java/me/joeleoli/praxi/arena/Arena.java new file mode 100644 index 0000000..b3423cc --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/Arena.java @@ -0,0 +1,100 @@ +package me.joeleoli.praxi.arena; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.praxi.cuboid.Cuboid; +import me.joeleoli.praxi.ladder.Ladder; +import org.bukkit.Location; + +@Getter +@Setter +public class Arena extends Cuboid { + + @Getter + private static List arenas = new ArrayList<>(); + + protected String name; + protected Location spawn1; + protected Location spawn2; + protected boolean active; + private ArenaType type; + private List ladders = new ArrayList<>(); + + public Arena(String name, ArenaType type, Location location1, Location location2) { + super(location1, location2); + + this.name = name; + this.type = type; + } + + public static Arena getByName(String name) { + for (Arena arena : arenas) { + if (arena.getType() != ArenaType.DUPLICATE && arena.getName() != null && + arena.getName().equalsIgnoreCase(name)) { + return arena; + } + } + + return null; + } + + public static Arena getRandom(Ladder ladder) { + final List _arenas = arenas.stream().filter(arena -> arena.isSetup() && + arena.getLadders().contains(ladder.getName()) && + ((ladder.isBuild() && !arena.isActive() && + (arena.getType() == ArenaType.STANDALONE || + arena.getType() == ArenaType.DUPLICATE)) || + (!ladder.isBuild() && + arena.getType() == ArenaType.SHARED))) + .collect(Collectors.toList()); + + if (_arenas.isEmpty()) { + return null; + } + + return _arenas.get(ThreadLocalRandom.current().nextInt(_arenas.size())); + } + + public int getMaxBuildHeight() { + int highest = (int) (this.spawn1.getY() >= this.spawn2.getY() ? this.spawn1.getY() : this.spawn2.getY()); + + return highest + 5; + } + + public Location getSpawn1() { + if (this.spawn1 == null) { + return null; + } + + return this.spawn1.clone(); + } + + public Location getSpawn2() { + if (this.spawn2 == null) { + return null; + } + + return this.spawn2.clone(); + } + + public void setActive(boolean active) { + if (this.type != ArenaType.SHARED) { + this.active = active; + } + } + + public void save() { + } + + public void delete() { + } + + public boolean isSetup() { + return this.spawn1 != null && this.spawn2 != null; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/ArenaType.java b/plugin/src/main/java/me/joeleoli/praxi/arena/ArenaType.java new file mode 100644 index 0000000..3306eae --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/ArenaType.java @@ -0,0 +1,12 @@ +package me.joeleoli.praxi.arena; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ArenaType { + STANDALONE, + SHARED, + DUPLICATE +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/SharedArena.java b/plugin/src/main/java/me/joeleoli/praxi/arena/SharedArena.java new file mode 100644 index 0000000..e95d9b9 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/SharedArena.java @@ -0,0 +1,46 @@ +package me.joeleoli.praxi.arena; + +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.util.LocationUtil; +import me.joeleoli.praxi.Praxi; +import org.bukkit.Location; + +@Getter +@Setter +public class SharedArena extends Arena { + + public SharedArena(String name, Location location1, Location location2) { + super(name, ArenaType.SHARED, location1, location2); + } + + @Override + public void save() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getArenaConfig(), "arenas." + this.name); + + cursor.set(null); + cursor.set("type", ArenaType.SHARED.name()); + + if (this.spawn1 != null) { + cursor.set("spawn1", LocationUtil.serialize(this.spawn1)); + } + + if (this.spawn2 != null) { + cursor.set("spawn2", LocationUtil.serialize(this.spawn2)); + } + + cursor.set("cuboid.location1", LocationUtil.serialize(this.getLowerCorner())); + cursor.set("cuboid.location2", LocationUtil.serialize(this.getUpperCorner())); + cursor.save(); + } + + @Override + public void delete() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getArenaConfig(), "arenas." + this.name); + + cursor.set(null); + cursor.save(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/StandaloneArena.java b/plugin/src/main/java/me/joeleoli/praxi/arena/StandaloneArena.java new file mode 100644 index 0000000..edef1f1 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/StandaloneArena.java @@ -0,0 +1,58 @@ +package me.joeleoli.praxi.arena; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.Getter; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.util.LocationUtil; +import me.joeleoli.praxi.Praxi; +import org.bukkit.Location; + +@Getter +public class StandaloneArena extends Arena { + + private List duplicates = new ArrayList<>(); + + public StandaloneArena(String name, Location location1, Location location2) { + super(name, ArenaType.STANDALONE, location1, location2); + } + + @Override + public void save() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getArenaConfig(), "arenas." + this.name); + + cursor.set(null); + cursor.set("type", ArenaType.STANDALONE.name()); + cursor.set("spawn1", LocationUtil.serialize(this.spawn1)); + cursor.set("spawn2", LocationUtil.serialize(this.spawn2)); + cursor.set("cuboid.location1", LocationUtil.serialize(this.getLowerCorner())); + cursor.set("cuboid.location2", LocationUtil.serialize(this.getUpperCorner())); + cursor.set("ladders", this.getLadders()); + + if (!this.duplicates.isEmpty()) { + AtomicInteger i = new AtomicInteger(); + + this.duplicates.forEach(duplicate -> { + cursor.setPath("arenas." + this.name + ".duplicates." + i.intValue()); + cursor.set("cuboid.location1", LocationUtil.serialize(duplicate.getLowerCorner())); + cursor.set("cuboid.location2", LocationUtil.serialize(duplicate.getUpperCorner())); + cursor.set("spawn1", LocationUtil.serialize(duplicate.getSpawn1())); + cursor.set("spawn2", LocationUtil.serialize(duplicate.getSpawn2())); + + i.getAndIncrement(); + }); + } + + cursor.save(); + } + + @Override + public void delete() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getArenaConfig(), "arenas." + this.name); + + cursor.set(null); + cursor.save(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/generator/ArenaGenerator.java b/plugin/src/main/java/me/joeleoli/praxi/arena/generator/ArenaGenerator.java new file mode 100644 index 0000000..880d01c --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/generator/ArenaGenerator.java @@ -0,0 +1,176 @@ +package me.joeleoli.praxi.arena.generator; + +import com.boydti.fawe.util.TaskManager; +import java.io.File; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.arena.ArenaType; +import me.joeleoli.praxi.arena.SharedArena; +import me.joeleoli.praxi.arena.StandaloneArena; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; + +@AllArgsConstructor +public class ArenaGenerator { + + private String name; + private World world; + private Schematic schematic; + private ArenaType type; + + public void generate(File file, StandaloneArena parentArena) { + if (Arena.getByName(this.name) != null) { + this.name = name + Nucleus.RANDOM.nextInt(1000); + } + + this.log("Generating " + this.type.name() + " " + this.name + " arena..."); + + int range = 500; + int attempts = 0; + + int preciseX = Nucleus.RANDOM.nextInt(range); + int preciseZ = Nucleus.RANDOM.nextInt(range); + + if (Nucleus.RANDOM.nextBoolean()) { + preciseX = -preciseX; + } + + if (Nucleus.RANDOM.nextBoolean()) { + preciseZ = -preciseZ; + } + + top: + while (true) { + attempts++; + + if (attempts >= 5) { + preciseX = Nucleus.RANDOM.nextInt(range); + preciseZ = Nucleus.RANDOM.nextInt(range); + + if (Nucleus.RANDOM.nextBoolean()) { + preciseX = -preciseX; + } + + if (Nucleus.RANDOM.nextBoolean()) { + preciseZ = -preciseZ; + } + + range += 500; + + this.log("Increased range to: " + range); + } + + if (this.world.getBlockAt(preciseX, 72, preciseZ) == null) { + continue; + } + + final int minX = preciseX - this.schematic.getClipBoard().getWidth() - 200; + final int maxX = preciseX + this.schematic.getClipBoard().getWidth() + 200; + final int minZ = preciseZ - this.schematic.getClipBoard().getLength() - 200; + final int maxZ = preciseZ + this.schematic.getClipBoard().getLength() + 200; + final int minY = 72; + final int maxY = 72 + this.schematic.getClipBoard().getHeight(); + + for (int x = minX; x < maxX; x++) { + for (int z = minZ; z < maxZ; z++) { + for (int y = minY; y < maxY; y++) { + if (this.world.getBlockAt(x, y, z).getType() != Material.AIR) { + continue top; + } + } + } + } + + final Location minCorner = new Location(this.world, minX, minY, minZ); + final Location maxCorner = new Location(this.world, maxX, maxY, maxZ); + + final int finalPreciseX = preciseX; + final int finalPreciseZ = preciseZ; + + TaskManager.IMP.async(() -> { + try { + new Schematic(file).pasteSchematic(this.world, finalPreciseX, 76, finalPreciseZ); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + Thread.sleep(1000); + } catch (Exception e) { + e.printStackTrace(); + } + + final Arena arena; + + if (this.type == ArenaType.STANDALONE) { + arena = new StandaloneArena(this.name, minCorner, maxCorner); + + this.type = ArenaType.DUPLICATE; + + for (int i = 0; i < 5; i++) { + TaskUtil.run(() -> this.generate(file, (StandaloneArena) arena)); + } + } else if (this.type == ArenaType.DUPLICATE) { + arena = new Arena(this.name, ArenaType.DUPLICATE, minCorner, maxCorner); + + parentArena.getDuplicates().add(arena); + } else { + arena = new SharedArena(this.name, minCorner, maxCorner); + } + + helper: + for (int x = minX; x < maxX; x++) { + for (int z = minZ; z < maxZ; z++) { + for (int y = minY; y < maxY; y++) { + if (this.world.getBlockAt(x, y, z).getType() == Material.SPONGE) { + final Block origin = this.world.getBlockAt(x, y, z); + final Block up = origin.getRelative(BlockFace.UP, 1); + + if (up.getState() instanceof Sign) { + final Sign sign = (Sign) up.getState(); + final float pitch = Float.valueOf(sign.getLine(0)); + final float yaw = Float.valueOf(sign.getLine(1)); + final Location loc = + new Location(origin.getWorld(), origin.getX(), origin.getY(), origin.getZ(), + yaw, pitch + ); + + TaskUtil.run(() -> { + up.setType(Material.AIR); + origin.setType(origin.getRelative(BlockFace.NORTH).getType()); + }); + + if (arena.getSpawn1() == null) { + arena.setSpawn1(loc); + } else if (arena.getSpawn2() == null) { + arena.setSpawn2(loc); + break helper; + } + } + } + } + } + } + + arena.save(); + + Arena.getArenas().add(arena); + }); + + this.log(String.format("Pasted schematic at %1$s, %2$s, %3$s", preciseX, 76, preciseZ)); + + return; + } + } + + private void log(String message) { + Nucleus.getInstance().getLogger().info("[ArenaGen] " + message); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/generator/Schematic.java b/plugin/src/main/java/me/joeleoli/praxi/arena/generator/Schematic.java new file mode 100644 index 0000000..ed8695c --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/generator/Schematic.java @@ -0,0 +1,42 @@ +package me.joeleoli.praxi.arena.generator; + +import com.sk89q.worldedit.CuboidClipboard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.schematic.SchematicFormat; +import com.sk89q.worldedit.world.DataException; +import java.io.File; +import java.io.IOException; +import lombok.Getter; +import me.joeleoli.nucleus.Nucleus; +import org.bukkit.World; + +@Getter +public class Schematic { + + private CuboidClipboard clipBoard; + + public Schematic(File file) throws IOException { + SchematicFormat format = SchematicFormat.MCEDIT; + + try { + this.clipBoard = format.load(file); + } catch (DataException e) { + Nucleus.getInstance().getLogger().severe("Failed to load schematic..."); + } + } + + public void pasteSchematic(World world, int x, int y, int z) { + Vector pastePos = new Vector(x, y, z); + EditSession editSession = new EditSession(new BukkitWorld(world), 999999); + + try { + this.clipBoard.place(editSession, pastePos, true); + } catch (MaxChangedBlocksException e) { + e.printStackTrace(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/arena/selection/Selection.java b/plugin/src/main/java/me/joeleoli/praxi/arena/selection/Selection.java new file mode 100644 index 0000000..b8897a3 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/arena/selection/Selection.java @@ -0,0 +1,91 @@ +package me.joeleoli.praxi.arena.selection; + +import java.util.Arrays; +import lombok.Data; +import lombok.NonNull; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.cuboid.Cuboid; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; + +@Data +public class Selection { + + public static final ItemStack SELECTION_WAND; + private static final String SELECTION_METADATA_KEY = "CLAIM_SELECTION"; + + static { + ItemStack itemStack = new ItemStack(Material.GOLD_AXE); + ItemMeta itemMeta = itemStack.getItemMeta(); + + itemMeta.setDisplayName(Style.GOLD + Style.BOLD + "Selection Wand"); + itemMeta.setLore(Arrays.asList( + Style.YELLOW + "Left-click to set position 1.", + Style.YELLOW + "Right-click to set position 2." + )); + itemStack.setItemMeta(itemMeta); + + SELECTION_WAND = itemStack; + } + + @NonNull + private Location point1; + @NonNull + private Location point2; + + /** + * Private, so that we can create a new instance in the Selection#createOrGetSelection method. + */ + private Selection() { + } + + /** + * Selections are stored in the player's metadata. This method removes the need to active Bukkit Metadata API calls + * all over the place. + *

+ * This method can be modified structurally as needed, the plugin only accepts Selection objects via this method. + * + * @param player the player for whom to grab the Selection object for + * + * @return selection object, either new or created + */ + public static Selection createOrGetSelection(Player player) { + if (player.hasMetadata(SELECTION_METADATA_KEY)) { + return (Selection) player.getMetadata(SELECTION_METADATA_KEY).get(0).value(); + } + + Selection selection = new Selection(); + + player.setMetadata(SELECTION_METADATA_KEY, new FixedMetadataValue(Praxi.getInstance(), selection)); + + return selection; + } + + /** + * @return the cuboid + */ + public Cuboid getCuboid() { + return new Cuboid(point1, point2); + } + + /** + * @return if the Selection can form a full cuboid object + */ + public boolean isFullObject() { + return point1 != null && point2 != null; + } + + /** + * Resets both locations in the Selection + */ + public void clear() { + point1 = null; + point2 = null; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/board/PracticeBoardAdapter.java b/plugin/src/main/java/me/joeleoli/praxi/board/PracticeBoardAdapter.java new file mode 100644 index 0000000..6a5e84b --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/board/PracticeBoardAdapter.java @@ -0,0 +1,168 @@ +package me.joeleoli.praxi.board; + +import java.util.ArrayList; +import java.util.List; +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.board.Board; +import me.joeleoli.nucleus.board.BoardAdapter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TimeUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.player.PracticeSetting; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.queue.Queue; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scoreboard.Scoreboard; + +public class PracticeBoardAdapter implements BoardAdapter { + + @Override + public String getTitle(Player player) { + if (Nucleus.getInstance().getRave() != null) { + return Nucleus.getInstance().getRave().getRaveTask().getTitle(); + } + + return Style.PINK + Style.BOLD + "MineXD "; + } + + @Override + public List getScoreboard(Player player, Board board) { + if (Nucleus.getInstance().getRave() != null) { + return Nucleus.getInstance().getRave().getRaveTask().getLines(); + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (!NucleusAPI.getSetting(player, PracticeSetting.SHOW_SCOREBOARD)) { + return null; + } + + final List toReturn = new ArrayList<>(); + + if (praxiPlayer.isInLobby()) { + toReturn.add(Style.YELLOW + "Online: " + Style.PINK + Bukkit.getOnlinePlayers().size()); + toReturn.add(Style.YELLOW + "Fighting: " + Style.PINK + Praxi.getInstance().getFightingCount()); + toReturn.add(Style.YELLOW + "Queueing: " + Style.PINK + Praxi.getInstance().getQueueingCount()); + + if (praxiPlayer.getParty() != null) { + toReturn.add(Style.YELLOW + "Your Party: " + Style.PINK + praxiPlayer.getParty().getTeamPlayers().size()); + } + + if (!Praxi.getInstance().getEventManager().getEventCooldown().hasExpired()) { + toReturn.add(Style.YELLOW + "Event Cooldown: " + Style.PINK + TimeUtil.millisToTimer( + Praxi.getInstance().getEventManager().getEventCooldown().getRemaining())); + } + } else if (praxiPlayer.isInQueue()) { + final Queue queue = praxiPlayer.getQueuePlayer().getQueue(); + + toReturn.add(Style.YELLOW + "Queue:"); + toReturn.add(" " + Style.PINK + (queue.isRanked() ? "Ranked" : "Unranked") + " " + + queue.getLadder().getName()); + toReturn.add(Style.YELLOW + "Time:"); + toReturn.add(" " + Style.PINK + TimeUtil.millisToTimer(praxiPlayer.getQueuePlayer().getPassed())); + + if (queue.isRanked()) { + toReturn.add(Style.YELLOW + "Range:"); + toReturn.add(" " + Style.PINK + praxiPlayer.getQueuePlayer().getMinRange() + " -> " + + praxiPlayer.getQueuePlayer().getMaxRange()); + } + } else if (praxiPlayer.isInMatch()) { + final Match match = praxiPlayer.getMatch(); + + if (match == null) { + return null; + } + + if (match.isSoloMatch()) { + final MatchPlayer opponent = match.getOpponentMatchPlayer(player); + + toReturn.add(Style.YELLOW + "Opponent: " + Style.PINK + opponent.getName()); + toReturn.add(Style.YELLOW + "Duration: " + Style.PINK + match.getDuration()); + + if (match.isFighting()) { + toReturn.add(""); + toReturn.add(Style.YELLOW + "Your Ping: " + Style.PINK + player.getPing() + "ms"); + toReturn.add(Style.YELLOW + "Their Ping: " + Style.PINK + opponent.getPing() + "ms"); + } + } else if (match.isTeamMatch()) { + toReturn.add(Style.YELLOW + "Duration: " + Style.PINK + match.getDuration()); + toReturn.add(Style.YELLOW + "Opponents: " + Style.PINK + match.getOpponentsLeft(player) + "/" + + match.getOpponentTeam(player).getTeamPlayers().size()); + + if (match.getTeam(player).getTeamPlayers().size() >= 8) { + toReturn.add(Style.YELLOW + "Your Team: " + Style.PINK + match.getTeam(player).getTeamPlayers().size()); + } else { + toReturn.add(""); + toReturn.add(Style.YELLOW + "Your Team:"); + + match.getTeam(player).getTeamPlayers().forEach(teamPlayer -> { + toReturn.add(" " + (teamPlayer.isDisconnected() || !teamPlayer.isAlive() ? Style.STRIKE_THROUGH + : "") + teamPlayer.getName()); + }); + } + } + } else if (praxiPlayer.isSpectating()) { + final Match match = praxiPlayer.getMatch(); + + toReturn.add(Style.YELLOW + "Ladder: " + Style.PINK + match.getLadder().getName()); + toReturn.add(Style.YELLOW + "Duration: " + Style.PINK + match.getDuration()); + toReturn.add(Style.YELLOW + "Players:"); + + if (match.isSoloMatch()) { + toReturn.add(" " + match.getMatchPlayerA().getName() + Style.GRAY + " (" + match.getMatchPlayerA().getPing() + ")"); + toReturn.add(" " + match.getMatchPlayerB().getName() + Style.GRAY + " (" + match.getMatchPlayerB().getPing() + ")"); + } else { + toReturn.add(" " + match.getTeamA().getLeader().getName() + "'s Team"); + toReturn.add(" " + match.getTeamA().getLeader().getName() + "'s Team"); + } + } else if (praxiPlayer.isInEvent()) { + final Event event = praxiPlayer.getEvent(); + + toReturn.add(Style.YELLOW + "Event: " + Style.PINK + "Sumo"); + + if (event.isWaiting()) { + toReturn.add(Style.YELLOW + "Players: " + Style.PINK + event.getEventPlayers().size() + "/" + event.getMaxPlayers()); + toReturn.add(""); + + if (event.getCooldown() == null) { + toReturn.add(Style.GRAY + Style.ITALIC + "Waiting for players..."); + } else { + toReturn.add(Style.GRAY + Style.ITALIC + "Starting in " + + TimeUtil.millisToSeconds(event.getCooldown().getRemaining()) + "s"); + } + } else { + toReturn.add(Style.YELLOW + "Remaining: " + Style.PINK + event.getRemainingPlayers() + "/" + event.getMaxPlayers()); + toReturn.add(Style.YELLOW + "Duration: " + Style.PINK + event.getRoundDuration()); + toReturn.add(Style.YELLOW + "Players:"); + toReturn.add(" " + event.getRoundPlayerA().getName() + Style.GRAY + " (" + event.getRoundPlayerA().getPing() + " ms)"); + toReturn.add(" " + event.getRoundPlayerB().getName() + Style.GRAY + " (" + event.getRoundPlayerB().getPing() + " ms)"); + } + } + + toReturn.add(0, Style.BORDER_LINE_SCOREBOARD); + toReturn.add(""); + toReturn.add(Style.PINK + "minexd.com"); + toReturn.add(Style.BORDER_LINE_SCOREBOARD); + + return toReturn; + } + + @Override + public long getInterval() { + return 2L; + } + + @Override + public void preLoop() { + } + + @Override + public void onScoreboardCreate(Player player, Scoreboard scoreboard) { + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/ArenaCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/ArenaCommands.java new file mode 100644 index 0000000..a94a607 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/ArenaCommands.java @@ -0,0 +1,207 @@ +package me.joeleoli.praxi.command; + +import java.io.File; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.CommandHelp; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.arena.ArenaType; +import me.joeleoli.praxi.arena.SharedArena; +import me.joeleoli.praxi.arena.StandaloneArena; +import me.joeleoli.praxi.arena.generator.ArenaGenerator; +import me.joeleoli.praxi.arena.generator.Schematic; +import me.joeleoli.praxi.arena.selection.Selection; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ArenaCommands { + + private static final CommandHelp[] HELP = new CommandHelp[]{ + new CommandHelp("/arena list", "List all arenas"), + new CommandHelp("/arena create ", "Create an arena"), + new CommandHelp("/arena delete ", "Delete an arena"), + new CommandHelp("/arena setspawn <1:2> ", "Set a spawn point"), + }; + + @Command(names = { "arena", "arena help" }, permissionNode = "praxi.arena") + public static void help(Player player) { + for (CommandHelp help : HELP) { + player.sendMessage( + Style.YELLOW + help.getSyntax() + Style.GRAY + " - " + Style.PINK + help.getDescription()); + } + } + + @Command(names = "arena wand", permissionNode = "praxi.arena") + public static void wand(Player player) { + player.getInventory().addItem(Selection.SELECTION_WAND); + player.sendMessage(Style.YELLOW + "You have been given the selection wand."); + } + + @Command(names = "arena save", permissionNode = "praxi.arena") + public static void save(CommandSender sender) { + Arena.getArenas().forEach(Arena::save); + sender.sendMessage(Style.GREEN + "Saved all arenas."); + } + + @Command(names = "arena list", permissionNode = "praxi.arena") + public static void list(Player player) { + player.sendMessage(Style.GOLD + "Arenas:"); + + if (Arena.getArenas().isEmpty()) { + player.sendMessage(Style.GRAY + "There are no arenas."); + return; + } + + for (Arena arena : Arena.getArenas()) { + if (arena.getType() != ArenaType.DUPLICATE) { + player.sendMessage(Style.GRAY + " - " + (arena.isSetup() ? Style.GREEN : Style.RED) + arena.getName() + + Style.GRAY + " (" + arena.getType().name() + ")"); + } + } + } + + @Command(names = "arena create", permissionNode = "praxi.arena") + public static void create(Player player, @Parameter(name = "name") String name, + @Parameter(name = "type") ArenaType type) { + Arena arena = Arena.getByName(name); + + if (arena != null) { + player.sendMessage(Style.RED + "An arena with that name already exists."); + return; + } + + Selection selection = Selection.createOrGetSelection(player); + + if (!selection.isFullObject()) { + player.sendMessage(Style.RED + "You must have a full selection to create an arena."); + return; + } + + if (type == ArenaType.STANDALONE) { + arena = new StandaloneArena(name, selection.getPoint1(), selection.getPoint2()); + } else { + arena = new SharedArena(name, selection.getPoint1(), selection.getPoint2()); + } + + arena.save(); + + Arena.getArenas().add(arena); + + player.sendMessage(Style.GREEN + "Arena `" + arena.getName() + "` has been created."); + } + + @Command(names = "arena delete", permissionNode = "praxi.arena") + public static void delete(Player player, @Parameter(name = "arena") Arena arena) { + arena.delete(); + + Arena.getArenas().remove(arena); + + if (arena instanceof StandaloneArena) { + Arena.getArenas().removeAll(((StandaloneArena) arena).getDuplicates()); + } + + player.sendMessage(Style.GREEN + "Arena `" + arena.getName() + "` has been deleted."); + } + + @Command(names = "arena setspawn", permissionNode = "praxi.arena") + public static void setSpawn(Player player, @Parameter(name = "loc") int loc, + @Parameter(name = "arena") Arena arena) { + if (loc == 1) { + arena.setSpawn1(player.getLocation()); + } else if (loc == 2) { + arena.setSpawn2(player.getLocation()); + } else { + player.sendMessage(Style.RED + "Choose position `1` or position `2`."); + return; + } + + arena.save(); + + player.sendMessage(Style.GREEN + "You set the spawn position " + loc + (loc == 1 ? "st" : "nd") + " for `" + + arena.getName() + "`."); + } + + @Command(names = "arena generate", permissionNode = "praxi.arena.generate", async = true) + public static void generate(Player player) { + File schematicsFolder = new File(Praxi.getInstance().getDataFolder().getPath() + File.separator + "schematics"); + + if (!schematicsFolder.exists()) { + player.sendMessage(Style.RED + "The schematics folder does not exist."); + return; + } + + for (File file : schematicsFolder.listFiles()) { + if (!file.isDirectory()) { + if (file.getName().contains(".schematic")) { + final boolean duplicate = file.getName().endsWith("_duplicate.schematic"); + + final String name = file.getName() + .replace(".schematic", "") + .replace("_duplicate", ""); + + final Arena parent = Arena.getByName(name); + + if (parent != null) { + if (!(parent instanceof StandaloneArena)) { + continue; + } + } + + TaskUtil.run(() -> { + try { + new ArenaGenerator(name, Bukkit.getWorlds().get(0), new Schematic(file), + duplicate ? (parent != null ? ArenaType.DUPLICATE : ArenaType.STANDALONE) + : ArenaType.SHARED + ).generate(file, (StandaloneArena) parent); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } + } + + player.sendMessage(Style.GREEN + "Generating arenas... See console for details."); + } + + @Command(names = "arena tp", permissionNode = "praxi.arena") + public static void teleport(Player player, @Parameter(name = "arena") Arena arena) { + if (arena.getSpawn1() != null) { + player.teleport(arena.getSpawn1()); + } else if (arena.getSpawn2() != null) { + player.teleport(arena.getSpawn2()); + } else { + player.teleport(arena.getUpperCorner()); + } + + player.sendMessage(Style.GREEN + "You teleported to " + Style.AQUA + arena.getName() + Style.GREEN + "."); + } + + @Command(names = "arena genhelper", permissionNode = "praxi.arena.genhelp") + public static void generatorHelper(Player player) { + final Block origin = player.getLocation().getBlock(); + final Block up = origin.getRelative(BlockFace.UP); + + origin.setType(Material.SPONGE); + up.setType(Material.SIGN_POST); + + if (up.getState() instanceof Sign) { + final Sign sign = (Sign) up.getState(); + + sign.setLine(0, ((int) player.getLocation().getPitch()) + ""); + sign.setLine(1, ((int) player.getLocation().getYaw()) + ""); + sign.update(); + + player.sendMessage(Style.GREEN + "Generator helper placed."); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/DuelCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/DuelCommands.java new file mode 100644 index 0000000..133bdc3 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/DuelCommands.java @@ -0,0 +1,158 @@ +package me.joeleoli.praxi.command; + +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.duel.DuelProcedure; +import me.joeleoli.praxi.duel.DuelRequest; +import me.joeleoli.praxi.duel.gui.DuelSelectLadderMenu; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.impl.SoloMatch; +import me.joeleoli.praxi.player.PracticeSetting; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.player.RematchData; +import org.bukkit.entity.Player; + +public class DuelCommands { + + @Command(names = "duel") + public static void duel(Player player, @Parameter(name = "target") Player target) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot duel while frozen."); + return; + } + + if (NucleusAPI.isFrozen(target)) { + player.sendMessage(Style.RED + "You cannot duel a frozen player."); + return; + } + + if (player.getUniqueId().equals(target.getUniqueId())) { + player.sendMessage(Style.RED + "You cannot duel yourself."); + return; + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final PraxiPlayer targetData = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (praxiPlayer.isBusy()) { + player.sendMessage(Style.RED + "You cannot duel right now."); + return; + } + + if (targetData.isBusy()) { + player.sendMessage(NucleusAPI.getColoredName(target) + Style.RED + " is currently busy."); + return; + } + + if (!NucleusAPI.getSetting(target, PracticeSetting.RECEIVE_DUEL_REQUESTS)) { + player.sendMessage(Style.RED + "That player is not accepting duel requests at the moment."); + return; + } + + if (!praxiPlayer.canSendDuelRequest(player)) { + player.sendMessage(Style.RED + "You have already sent that player a duel request."); + return; + } + + DuelProcedure procedure = new DuelProcedure(); + + procedure.setSender(player); + procedure.setTarget(target); + + praxiPlayer.setDuelProcedure(procedure); + + new DuelSelectLadderMenu().openMenu(player); + } + + @Command(names = "duel accept") + public static void accept(Player player, @Parameter(name = "target") Player target) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot duel while frozen."); + return; + } + + if (NucleusAPI.isFrozen(target)) { + player.sendMessage(Style.RED + "You cannot duel a frozen player."); + return; + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final PraxiPlayer targetData = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (!targetData.isPendingDuelRequest(player)) { + player.sendMessage(Style.RED + "You do not have a pending duel request from " + NucleusAPI.getColoredName(target) + Style.RED + "."); + return; + } + + if (praxiPlayer.isBusy()) { + player.sendMessage(Style.RED + "You cannot duel right now."); + return; + } + + if (targetData.isBusy()) { + player.sendMessage(NucleusAPI.getColoredName(target) + Style.RED + " is currently busy."); + return; + } + + final DuelRequest request = targetData.getSentDuelRequests().get(player.getUniqueId()); + + Arena arena = request.getArena(); + + if (arena.isActive()) { + arena = Arena.getRandom(request.getLadder()); + } + + if (arena == null) { + player.sendMessage(Style.RED + "Tried to start a match but there are no available arenas."); + return; + } + + arena.setActive(true); + + Match match = new SoloMatch(new MatchPlayer(player), new MatchPlayer(target), request.getLadder(), arena, false, + true + ); + + match.handleStart(); + } + + @Command(names = "rematch") + public static void rematch(Player player) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot duel while frozen."); + return; + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getRematchData() == null) { + player.sendMessage(Style.RED + "You do not have anyone to re-match."); + return; + } + + praxiPlayer.refreshHotbar(); + + if (praxiPlayer.getRematchData() == null) { + player.sendMessage(Style.RED + "That player is no longer available."); + return; + } + + final RematchData rematchData = praxiPlayer.getRematchData(); + + if (rematchData.isReceive()) { + rematchData.accept(); + } else { + if (rematchData.isSent()) { + player.sendMessage(Style.RED + "You have already sent a rematch request to that player."); + return; + } + + rematchData.request(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/EventCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/EventCommands.java new file mode 100644 index 0000000..edb452a --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/EventCommands.java @@ -0,0 +1,127 @@ +package me.joeleoli.praxi.command; + +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.events.impl.SumoEvent; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class EventCommands { + + @Command(names = "eventmanager cancel", permissionNode = "praxi.event.admin") + public static void cancel(CommandSender sender) { + if (Praxi.getInstance().getEventManager().getActiveEvent() == null) { + sender.sendMessage(Style.RED + "There is no active event."); + return; + } + + Praxi.getInstance().getEventManager().getActiveEvent().end(); + } + + @Command(names = "eventmanager cooldown", permissionNode = "praxi.event.admin") + public static void cooldown(CommandSender sender) { + if (Praxi.getInstance().getEventManager().getEventCooldown().hasExpired()) { + sender.sendMessage(Style.RED + "There is no event cooldown active."); + return; + } + + sender.sendMessage(Style.GREEN + "You reset the event cooldown."); + Praxi.getInstance().getEventManager().setEventCooldown(new Cooldown(0)); + } + + @Command(names = "eventmanager setspawn pos", permissionNode = "praxi.event.admin") + public static void setSpawnPosition(Player player, @Parameter(name = "pos") int position) { + if (!(position == 1 || position == 2)) { + player.sendMessage(Style.RED + "The position must be 1 or 2."); + } else { + if (position == 1) { + Praxi.getInstance().getEventManager().setSumoSpawn1(player.getLocation()); + } else { + Praxi.getInstance().getEventManager().setSumoSpawn2(player.getLocation()); + } + + Praxi.getInstance().getEventManager().save(); + player.sendMessage(Style.GREEN + "Updated event's spawn location " + position + "."); + } + } + + @Command(names = "eventmanager setspawn spec", permissionNode = "praxi.event.admin") + public static void setSpawnSpectator(Player player) { + Praxi.getInstance().getEventManager().setSumoSpectator(player.getLocation()); + Praxi.getInstance().getEventManager().save(); + player.sendMessage(Style.GREEN + "Updated event's spawn spectator location."); + } + + @Command(names = { "event host", "host" }, permissionNode = "praxi.event.host") + public static void hostEvent(Player player) { + if (Praxi.getInstance().getEventManager().getActiveEvent() != null) { + player.sendMessage(Style.RED + "There is already an active event."); + return; + } + + if (!Praxi.getInstance().getEventManager().getEventCooldown().hasExpired()) { + player.sendMessage(Style.RED + "There is an event cooldown active."); + return; + } + + Praxi.getInstance().getEventManager().setActiveEvent(new SumoEvent(player)); + + for (Player other : Praxi.getInstance().getServer().getOnlinePlayers()) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(other.getUniqueId()); + + if (praxiPlayer.isInLobby()) { + if (!praxiPlayer.getKitEditor().isActive()) { + praxiPlayer.loadHotbar(); + } + } + } + } + + @Command(names = { "event join" }) + public static void eventJoin(Player player) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final Event activeEvent = Praxi.getInstance().getEventManager().getActiveEvent(); + + if (praxiPlayer.isBusy()) { + player.sendMessage(Style.RED + "You cannot join the event right now."); + return; + } + + if (activeEvent == null) { + player.sendMessage(Style.RED + "There is no active event."); + return; + } + + if (activeEvent.getState() != EventState.WAITING) { + player.sendMessage(Style.RED + "That event is currently on-going and cannot be joined."); + return; + } + + Praxi.getInstance().getEventManager().getActiveEvent().handleJoin(player); + } + + @Command(names = { "event leave" }) + public static void eventLeave(Player player) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final Event activeEvent = Praxi.getInstance().getEventManager().getActiveEvent(); + + if (activeEvent == null) { + player.sendMessage(Style.RED + "There is no active event."); + return; + } + + if (!praxiPlayer.isInEvent() || !activeEvent.getEventPlayers().containsKey(player.getUniqueId())) { + player.sendMessage(Style.RED + "You are not apart of the active event."); + return; + } + + Praxi.getInstance().getEventManager().getActiveEvent().handleLeave(player); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/LadderCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/LadderCommands.java new file mode 100644 index 0000000..6e7dc0c --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/LadderCommands.java @@ -0,0 +1,109 @@ +package me.joeleoli.praxi.command; + +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.ragespigot.RageSpigot; +import me.joeleoli.ragespigot.knockback.KnockbackProfile; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class LadderCommands { + + @Command(names = "ladder enable", permissionNode = "praxi.ladder") + public static void enable(CommandSender sender, @Parameter(name = "ladder") Ladder ladder) { + ladder.setEnabled(true); + sender.sendMessage(Style.GREEN + "You enabled the " + ladder.getDisplayName() + Style.GREEN + " ladder."); + } + + @Command(names = "ladder disable", permissionNode = "praxi.ladder") + public static void disable(CommandSender sender, @Parameter(name = "ladder") Ladder ladder) { + ladder.setEnabled(false); + sender.sendMessage(Style.GREEN + "You disabled the " + ladder.getDisplayName() + Style.GREEN + " ladder."); + } + + @Command(names = "ladder sethitdelay", permissionNode = "praxi.ladder") + public static void setHitDelay(CommandSender sender, @Parameter(name = "ladder") Ladder ladder, + @Parameter(name = "hitdelay") int hitDelay) { + if (hitDelay < 0 || hitDelay > 20) { + sender.sendMessage(Style.RED + "The hit delay must be in the range of 0-20."); + return; + } + + ladder.setHitDelay(hitDelay); + sender.sendMessage(Style.GREEN + "You set the hit delay of " + ladder.getDisplayName() + Style.GREEN + " to: " + + Style.RESET + ladder.getHitDelay()); + } + + @Command(names = "ladder list", permissionNode = "praxi.ladder") + public static void list(CommandSender sender) { + sender.sendMessage(Style.GOLD + Style.BOLD + "Ladders:"); + + Ladder.getLadders().forEach(ladder -> { + sender.sendMessage(Style.GRAY + " - " + ladder.getDisplayName()); + }); + } + + @Command(names = "ladder create", permissionNode = "praxi.ladder") + public static void create(Player player, @Parameter(name = "name") String name) { + Ladder ladder = Ladder.getByName(name); + + if (ladder != null) { + player.sendMessage(Style.RED + "A ladder with that name already exists."); + return; + } + + ladder = new Ladder(name); + ladder.save(); + + player.sendMessage( + Style.GREEN + "Created a new ladder named " + Style.AQUA + ladder.getName() + Style.GREEN + "."); + } + + @Command(names = "ladder setkit", permissionNode = "praxi.ladder") + public static void setKit(Player player, @Parameter(name = "ladder") Ladder ladder) { + ladder.getDefaultKit().setArmor(player.getInventory().getArmorContents()); + ladder.getDefaultKit().setContents(player.getInventory().getContents()); + ladder.save(); + + player.sendMessage(Style.GREEN + "Updated " + Style.AQUA + ladder.getName() + Style.GREEN + "'s default kit."); + } + + @Command(names = "ladder loadkit", permissionNode = "praxi.ladder") + public static void loadKit(Player player, @Parameter(name = "ladder") Ladder ladder) { + player.getInventory().setArmorContents(ladder.getDefaultKit().getArmor()); + player.getInventory().setContents(ladder.getDefaultKit().getContents()); + player.updateInventory(); + player.sendMessage(Style.GREEN + "Loaded " + Style.AQUA + ladder.getName() + Style.GREEN + "'s default kit."); + } + + @Command(names = "ladder setdisplayname", permissionNode = "praxi.ladder") + public static void setDisplayName(CommandSender sender, @Parameter(name = "ladder") Ladder ladder, + @Parameter(name = "displayName") String displayName) { + ladder.setDisplayName(Style.translate(displayName)); + ladder.save(); + + sender.sendMessage( + Style.GREEN + "You set " + Style.AQUA + ladder.getName() + "'s display name " + Style.GREEN + "to: " + + Style.AQUA + Style.translate(displayName)); + } + + @Command(names = "ladder setkbprofile", permissionNode = "praxi.ladder") + public static void setKnockbackProfile(CommandSender sender, @Parameter(name = "ladder") Ladder ladder, + @Parameter(name = "profile") String profileName) { + final KnockbackProfile profile = RageSpigot.INSTANCE.getConfig().getKbProfileByName(profileName); + + if (profile == null) { + sender.sendMessage(Style.RED + "A knockback profile with that name could not be found."); + return; + } + + ladder.setKbProfile(profileName); + ladder.save(); + + sender.sendMessage( + Style.GREEN + "You set the kb-profile for " + ladder.getDisplayName() + Style.GREEN + " ladder."); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/ManagementCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/ManagementCommands.java new file mode 100644 index 0000000..e1f1256 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/ManagementCommands.java @@ -0,0 +1,68 @@ +package me.joeleoli.praxi.command; + +import java.util.UUID; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.CommandHelp; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.nucleus.uuid.UUIDCache; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ManagementCommands { + + private static final CommandHelp[] HELP = new CommandHelp[]{ + new CommandHelp("/praxi reload", "Reload the config"), + }; + + @Command(names = { "praxi", "praxi help" }, permissionNode = "praxi.admin") + public static void help(Player player) { + for (CommandHelp help : HELP) { + player.sendMessage( + Style.YELLOW + help.getSyntax() + Style.GRAY + " - " + Style.PINK + help.getDescription()); + } + } + + @Command(names = "resetelo", permissionNode = "prax.admin.resetelo") + public static void resetElo(CommandSender sender, @Parameter(name = "target") String targetName) { + UUID uuid; + + try { + uuid = UUID.fromString(targetName); + } catch (Exception e) { + uuid = UUIDCache.getUuid(targetName); + } + + if (uuid == null) { + sender.sendMessage( + Style.RED + "Couldn't find a player with the name " + Style.RESET + targetName + Style.RED + + ". Have they joined the network?"); + return; + } + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(uuid); + + if (praxiPlayer.isLoaded()) { + praxiPlayer.getStatistics().getLadders().values().forEach(stats -> { + stats.setElo(1000); + }); + + praxiPlayer.save(); + } else { + TaskUtil.runAsync(() -> { + praxiPlayer.load(); + + praxiPlayer.getStatistics().getLadders().values().forEach(stats -> { + stats.setElo(1000); + }); + + praxiPlayer.save(); + }); + } + + sender.sendMessage(Style.GREEN + "You reset " + targetName + "'s elo."); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/MatchCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/MatchCommands.java new file mode 100644 index 0000000..31d3ec2 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/MatchCommands.java @@ -0,0 +1,31 @@ +package me.joeleoli.praxi.command; + +import java.util.UUID; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.match.MatchSnapshot; +import me.joeleoli.praxi.match.gui.MatchDetailsMenu; +import org.bukkit.entity.Player; + +public class MatchCommands { + + @Command(names = { "viewinventory", "viewinv" }) + public static void viewInventory(Player player, @Parameter(name = "id") String id) { + MatchSnapshot cachedInventory; + + try { + cachedInventory = MatchSnapshot.getByUuid(UUID.fromString(id)); + } catch (Exception e) { + cachedInventory = MatchSnapshot.getByName(id); + } + + if (cachedInventory == null) { + player.sendMessage(Style.RED + "Couldn't find an inventory for that ID."); + return; + } + + new MatchDetailsMenu(cachedInventory).openMenu(player); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/PartyCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/PartyCommands.java new file mode 100644 index 0000000..e32a8b3 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/PartyCommands.java @@ -0,0 +1,254 @@ +package me.joeleoli.praxi.command; + +import java.util.UUID; +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.CommandHelp; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.party.Party; +import me.joeleoli.praxi.party.PartyState; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class PartyCommands { + + private static final CommandHelp[] HELP = new CommandHelp[]{ + new CommandHelp("/party create", "Create a party"), + new CommandHelp("/party disband", "Disband your party"), + new CommandHelp("/party leave", "Leave your party"), + new CommandHelp("/party join ", "Join a party"), + new CommandHelp("/party kick ", "Kick a player from your party"), + new CommandHelp("/party open", "Make your party open"), + new CommandHelp("/party close", "Make your party closed"), + }; + + @Command(names = { "party", "party help" }) + public static void help(Player player) { + for (CommandHelp help : HELP) { + player.sendMessage( + Style.YELLOW + help.getSyntax() + Style.GRAY + " - " + Style.PINK + help.getDescription()); + } + } + + @Command(names = { "p create", "party create" }) + public static void create(Player player) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot create a party while frozen."); + return; + } + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() != null) { + player.sendMessage(Style.RED + "You already have a party."); + return; + } + + if (!praxiPlayer.isInLobby()) { + player.sendMessage(Style.RED + "You must be in the lobby to create a party."); + return; + } + + praxiPlayer.setParty(new Party(player)); + praxiPlayer.loadHotbar(); + + player.sendMessage(Style.YELLOW + "You created a new party."); + } + + @Command(names = { "p disband", "party disband" }) + public static void disband(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (!praxiPlayer.getParty().isLeader(player.getUniqueId())) { + player.sendMessage(Style.RED + "You are not the leader of your party."); + return; + } + + praxiPlayer.getParty().disband(); + } + + @Command(names = { "p invite", "party invite" }) + public static void invite(Player player, @Parameter(name = "target") Player target) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (!praxiPlayer.getParty().canInvite(target)) { + player.sendMessage(Style.RED + "That player has already been invited to your party."); + return; + } + + if (praxiPlayer.getParty().containsPlayer(target)) { + player.sendMessage(Style.RED + "That player is already in your party."); + return; + } + + if (praxiPlayer.getParty().getState() == PartyState.OPEN) { + player.sendMessage(Style.RED + "The party state is Open. You do not need to invite players."); + return; + } + + final PraxiPlayer targetData = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (targetData.isBusy()) { + player.sendMessage(NucleusAPI.getColoredName(target) + Style.RED + " is currently busy."); + return; + } + + praxiPlayer.getParty().invite(target); + } + + @Command(names = { "p join", "party join" }) + public static void join(Player player, @Parameter(name = "target") String targetId) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot join a party while frozen."); + return; + } + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() != null) { + player.sendMessage(Style.RED + "You already have a party."); + return; + } + + Player target; + + try { + target = Bukkit.getPlayer(UUID.fromString(targetId)); + } catch (Exception e) { + target = Bukkit.getPlayer(targetId); + } + + if (target == null) { + player.sendMessage(Style.RED + "A player with that name could not be found."); + return; + } + + PraxiPlayer targetData = PraxiPlayer.getByUuid(target.getUniqueId()); + Party party = targetData.getParty(); + + if (party == null) { + player.sendMessage(Style.RED + "A party with that name could not be found."); + return; + } + + if (party.getState() == PartyState.CLOSED) { + if (!party.isInvited(player)) { + player.sendMessage(Style.RED + "You have not been invited to that party."); + return; + } + } + + if (party.getPlayers().size() >= 32) { + player.sendMessage(Style.RED + "That party is full and cannot hold anymore players."); + return; + } + + party.join(player); + } + + @Command(names = { "p leave", "party leave" }) + public static void leave(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (praxiPlayer.getParty().getLeader().getUuid().equals(player.getUniqueId())) { + praxiPlayer.getParty().disband(); + } else { + praxiPlayer.getParty().leave(player, false); + } + } + + @Command(names = { "p kick", "party kick" }) + public static void kick(Player player, @Parameter(name = "target") Player target) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (!praxiPlayer.getParty().isLeader(player.getUniqueId())) { + player.sendMessage(Style.RED + "You are not the leader of your party."); + return; + } + + if (!praxiPlayer.getParty().containsPlayer(target)) { + player.sendMessage(Style.RED + "That player is not a member of your party."); + return; + } + + if (player.equals(target)) { + player.sendMessage(Style.RED + "You cannot kick yourself from your party."); + return; + } + + praxiPlayer.getParty().leave(target, true); + } + + @Command(names = { "p close", "party close" }) + public static void open(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (!praxiPlayer.getParty().isLeader(player.getUniqueId())) { + player.sendMessage(Style.RED + "You are not the leader of your party."); + return; + } + + praxiPlayer.getParty().setState(PartyState.CLOSED); + } + + @Command(names = { "p open", "party open" }) + public static void close(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + if (!praxiPlayer.getParty().isLeader(player.getUniqueId())) { + player.sendMessage(Style.RED + "You are not the leader of your party."); + return; + } + + praxiPlayer.getParty().setState(PartyState.OPEN); + } + + @Command(names = { "p info", "party info", "party information" }) + public static void information(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (!praxiPlayer.isLoaded()) { + return; + } + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You do not have a party."); + return; + } + + praxiPlayer.getParty().sendInformation(player); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/PlayerCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/PlayerCommands.java new file mode 100644 index 0000000..c2107d5 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/PlayerCommands.java @@ -0,0 +1,73 @@ +package me.joeleoli.praxi.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.uuid.UUIDCache; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.player.gui.PlayerSettingsMenu; +import org.bukkit.entity.Player; + +public class PlayerCommands { + + @Command(names = "fly", permissionNode = "praxi.donor.fly") + public static void fly(Player player) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInLobby() || praxiPlayer.isInQueue()) { + player.setAllowFlight(true); + player.setFlying(true); + player.updateInventory(); + player.sendMessage(Style.YELLOW + "You are now flying."); + } else { + player.sendMessage(Style.RED + "You cannot fly right now."); + } + } + + @Command(names = { "settings", "options" }) + public static void settings(Player player) { + new PlayerSettingsMenu().openMenu(player); + } + + @Command(names = { "statistics", "stats" }, async = true) + public static void statistics(Player player, @Parameter(name = "target", defaultValue = "self") String name) { + if (name.equalsIgnoreCase("self")) { + name = player.getName(); + } + + final UUID uuid = UUIDCache.getUuid(name); + + if (uuid == null) { + player.sendMessage( + Style.RED + "Couldn't find a player with the name " + Style.RESET + name + Style.RED + "."); + return; + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(uuid); + + if (!praxiPlayer.isLoaded()) { + praxiPlayer.load(); + } + + if (praxiPlayer.getName() != null) { + if (praxiPlayer.getName().equalsIgnoreCase(name)) { + name = praxiPlayer.getName(); + } + } + + final List messages = new ArrayList<>(); + + praxiPlayer.getStatistics().getLadders().forEach((key, value) -> { + messages.add(Style.YELLOW + key + Style.GRAY + ": " + Style.PINK + value.getElo() + " ELO"); + }); + + messages.add(0, Style.GOLD + Style.BOLD + name + "'s Statistics"); + messages.add(0, Style.getBorderLine()); + messages.add(Style.getBorderLine()); + messages.forEach(player::sendMessage); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/SpectateCommands.java b/plugin/src/main/java/me/joeleoli/praxi/command/SpectateCommands.java new file mode 100644 index 0000000..f429906 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/SpectateCommands.java @@ -0,0 +1,58 @@ +package me.joeleoli.praxi.command; + +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.command.Command; +import me.joeleoli.nucleus.command.param.Parameter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.player.PracticeSetting; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; + +public class SpectateCommands { + + @Command(names = { "spectate", "spec" }) + public static void spectate(Player player, @Parameter(name = "target") Player target) { + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot spectate while frozen."); + return; + } + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + PraxiPlayer targetData = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (praxiPlayer.isBusy()) { + player.sendMessage(Style.RED + "You cannot spectate right now."); + return; + } + + if (praxiPlayer.getParty() != null) { + player.sendMessage(Style.RED + "You must leave your party to spectate a match."); + return; + } + + if (targetData == null || !targetData.isInMatch()) { + player.sendMessage(Style.RED + "That player is not in a match."); + return; + } + + if (!NucleusAPI.getSetting(target, PracticeSetting.RECEIVE_DUEL_REQUESTS)) { + player.sendMessage(Style.RED + "That player is not allowing spectators."); + return; + } + + targetData.getMatch().addSpectator(player, target); + } + + @Command(names = "stopspectate") + public static void stopSpectate(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer == null || !praxiPlayer.isSpectating()) { + player.sendMessage(Style.RED + "You are not spectating a match."); + return; + } + + praxiPlayer.getMatch().removeSpectator(player); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaParameterType.java b/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaParameterType.java new file mode 100644 index 0000000..a6ca5ca --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaParameterType.java @@ -0,0 +1,38 @@ +package me.joeleoli.praxi.command.param; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import me.joeleoli.nucleus.command.param.ParameterType; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import org.apache.commons.lang.StringUtils; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ArenaParameterType implements ParameterType { + + public Arena transform(CommandSender sender, String source) { + Arena arena = Arena.getByName(source); + + if (arena == null) { + sender.sendMessage(Style.RED + "An arena with that name does not exist."); + return null; + } + + return arena; + } + + public List tabComplete(Player sender, Set flags, String source) { + List completions = new ArrayList<>(); + + for (Arena arena : Arena.getArenas()) { + if (arena.getName() != null && StringUtils.startsWithIgnoreCase(arena.getName(), source)) { + completions.add(arena.getName()); + } + } + + return completions; + } + +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaTypeParameterType.java b/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaTypeParameterType.java new file mode 100644 index 0000000..0d7004b --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/param/ArenaTypeParameterType.java @@ -0,0 +1,37 @@ +package me.joeleoli.praxi.command.param; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import me.joeleoli.nucleus.command.param.ParameterType; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.ArenaType; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class ArenaTypeParameterType implements ParameterType { + + public ArenaType transform(CommandSender sender, String source) { + ArenaType type; + + try { + type = ArenaType.valueOf(source); + } catch (Exception e) { + sender.sendMessage(Style.RED + "That is not a valid arena type."); + return null; + } + + return type; + } + + public List tabComplete(Player sender, Set flags, String source) { + List completions = new ArrayList<>(); + + for (ArenaType type : ArenaType.values()) { + completions.add(type.name()); + } + + return completions; + } + +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/param/LadderParameterType.java b/plugin/src/main/java/me/joeleoli/praxi/command/param/LadderParameterType.java new file mode 100644 index 0000000..ebcbb36 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/param/LadderParameterType.java @@ -0,0 +1,35 @@ +package me.joeleoli.praxi.command.param; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import me.joeleoli.nucleus.command.param.ParameterType; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.ladder.Ladder; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class LadderParameterType implements ParameterType { + + public Ladder transform(CommandSender sender, String source) { + Ladder ladder = Ladder.getByName(source); + + if (ladder == null) { + sender.sendMessage(Style.RED + "That is not a valid ladder type."); + return null; + } + + return ladder; + } + + public List tabComplete(Player sender, Set flags, String source) { + List completions = new ArrayList<>(); + + for (Ladder ladder : Ladder.getLadders()) { + completions.add(ladder.getName()); + } + + return completions; + } + +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/command/param/QueueParameterType.java b/plugin/src/main/java/me/joeleoli/praxi/command/param/QueueParameterType.java new file mode 100644 index 0000000..313cc6e --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/command/param/QueueParameterType.java @@ -0,0 +1,35 @@ +package me.joeleoli.praxi.command.param; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import me.joeleoli.nucleus.command.param.ParameterType; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.queue.Queue; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class QueueParameterType implements ParameterType { + + public Queue transform(CommandSender sender, String source) { + try { + Queue queue = Queue.getByUuid(UUID.fromString(source)); + + if (queue == null) { + sender.sendMessage(Style.RED + "A queue with that ID does not exist."); + return null; + } + + return queue; + } catch (Exception e) { + sender.sendMessage(Style.RED + "A queue with that ID does not exist."); + return null; + } + } + + public List tabComplete(Player sender, Set flags, String source) { + return Collections.emptyList(); + } + +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/config/ConfigItem.java b/plugin/src/main/java/me/joeleoli/praxi/config/ConfigItem.java new file mode 100644 index 0000000..a0fb1e1 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/config/ConfigItem.java @@ -0,0 +1,62 @@ +package me.joeleoli.praxi.config; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.util.Style; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +@Getter +public class ConfigItem { + + private Material material = Material.AIR; + private short durability = 0; + private String name; + private List lore = new ArrayList<>(); + private int amount = 1; + + public ConfigItem(ConfigCursor cursor, String path) { + if (cursor.exists(path + ".material")) { + this.material = Material.valueOf(cursor.getString(path + ".material")); + } + + if (cursor.exists(path + ".durability")) { + this.durability = (short) cursor.getInt(path + ".durability"); + } + + if (cursor.exists(path + ".name")) { + this.name = Style.translate(cursor.getString(path + ".name")); + } + + if (cursor.exists(path + ".lore")) { + this.lore = Style.translateLines(cursor.getStringList(path + ".lore")); + } + + if (cursor.exists(path + ".amount")) { + this.amount = cursor.getInt(path + ".amount"); + } + } + + public ItemStack toItemStack() { + ItemStack itemStack = new ItemStack(this.material); + ItemMeta itemMeta = itemStack.getItemMeta(); + + if (this.name != null) { + itemMeta.setDisplayName(Style.translate(this.name)); + } + + if (this.lore != null && !this.lore.isEmpty()) { + itemMeta.setLore(Style.translateLines(this.lore)); + } + + itemStack.setAmount(this.amount); + itemStack.setDurability(this.durability); + itemStack.setItemMeta(itemMeta); + + return itemStack; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/cuboid/Cuboid.java b/plugin/src/main/java/me/joeleoli/praxi/cuboid/Cuboid.java new file mode 100644 index 0000000..35d5595 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/cuboid/Cuboid.java @@ -0,0 +1,519 @@ +package me.joeleoli.praxi.cuboid; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import lombok.Data; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; + +@Data +public class Cuboid implements Iterable { + + private String worldName; + private int x1, y1, z1; + private int x2, y2, z2; + + /** + * Construct a Cuboid given two Location objects which represent any two corners of the Cuboid. + * + * @param l1 one of the corners + * @param l2 the other corner + */ + public Cuboid(Location l1, Location l2) { + this(l1.getWorld().getName(), + l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), + l2.getBlockX(), l2.getBlockY(), l2.getBlockZ() + ); + + } + + /** + * Construct a Cuboid in the given World and xyz coords + * + * @param world the Cuboid's world + * @param x1 X coord of corner 1 + * @param y1 Y coord of corner 1 + * @param z1 Z coord of corner 1 + * @param x2 X coord of corner 2 + * @param y2 Y coord of corner 2 + * @param z2 Z coord of corner 2 + */ + public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) { + this(world.getName(), x1, y1, z1, x2, y2, z2); + } + + /** + * Construct a Cuboid in the given world name and xyz coords. + * + * @param worldName the Cuboid's world name + * @param x1 X coord of corner 1 + * @param y1 Y coord of corner 1 + * @param z1 Z coord of corner 1 + * @param x2 X coord of corner 2 + * @param y2 Y coord of corner 2 + * @param z2 Z coord of corner 2 + */ + public Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) { + this.worldName = worldName; + this.x1 = Math.min(x1, x2); + this.x2 = Math.max(x1, x2); + this.y1 = Math.min(y1, y2); + this.y2 = Math.max(y1, y2); + this.z1 = Math.min(z1, z2); + this.z2 = Math.max(z1, z2); + } + + /** + * Get the Location of the lower northeast corner of the Cuboid (minimum XYZ coords). + * + * @return Location of the lower northeast corner + */ + public Location getLowerCorner() { + return new Location(getWorld(), x1, y1, z1); + } + + /** + * Get the Location of the upper southwest corner of the Cuboid (maximum XYZ coords). + * + * @return Location of the upper southwest corner + */ + public Location getUpperCorner() { + return new Location(getWorld(), x2, y2, z2); + } + + /** + * Get the the center of the Cuboid + * + * @return Location at the centre of the Cuboid + */ + public Location getCenter() { + return new Location( + getWorld(), getLowerX() + (getUpperX() - getLowerX()) / 2, + getLowerY() + (getUpperY() - getLowerY()) / 2, getLowerZ() + (getUpperZ() - getLowerZ()) / 2 + ); + } + + /** + * Get the Cuboid's world. + * + * @return the World object representing this Cuboid's world + * + * @throws IllegalStateException if the world is not loaded + */ + public World getWorld() { + + World world = Bukkit.getWorld(worldName); + if (world == null) { + throw new IllegalStateException("world '" + worldName + "' is not loaded"); + } + return world; + } + + /** + * Get the size of this Cuboid along the X axis + * + * @return Size of Cuboid along the X axis + */ + public int getSizeX() { + return (x2 - x1) + 1; + } + + /** + * Get the size of this Cuboid along the Y axis + * + * @return Size of Cuboid along the Y axis + */ + public int getSizeY() { + return (y2 - y1) + 1; + } + + /** + * Get the size of this Cuboid along the Z axis + * + * @return Size of Cuboid along the Z axis + */ + public int getSizeZ() { + return (z2 - z1) + 1; + } + + /** + * Get the minimum X coord of this Cuboid + * + * @return the minimum X coord + */ + public int getLowerX() { + return x1; + } + + /** + * Get the minimum Y coord of this Cuboid + * + * @return the minimum Y coord + */ + public int getLowerY() { + return y1; + } + + /** + * Get the minimum Z coord of this Cuboid + * + * @return the minimum Z coord + */ + public int getLowerZ() { + return z1; + } + + /** + * Get the maximum X coord of this Cuboid + * + * @return the maximum X coord + */ + public int getUpperX() { + return x2; + } + + /** + * Get the maximum Y coord of this Cuboid + * + * @return the maximum Y coord + */ + public int getUpperY() { + return y2; + } + + /** + * Get the maximum Z coord of this Cuboid + * + * @return the maximum Z coord + */ + public int getUpperZ() { + return z2; + } + + /** + * Get the Blocks at the four corners of the Cuboid, without respect to y-value + * + * @return array of Block objects representing the Cuboid corners + */ + public Location[] getCorners() { + Location[] res = new Location[4]; + World w = getWorld(); + res[0] = new Location(w, x1, 0, z1); // ++x + res[1] = new Location(w, x2, 0, z1); // ++z + res[2] = new Location(w, x2, 0, z2); // --x + res[3] = new Location(w, x1, 0, z2); // --z + return res; + } + + /** + * Expand the Cuboid in the given direction by the given amount. Negative amounts will shrink the Cuboid in the + * given direction. Shrinking a cuboid's face past the opposite face is not an error and will return a valid + * Cuboid. + * + * @param dir the direction in which to expand + * @param amount the number of blocks by which to expand + * + * @return a new Cuboid expanded by the given direction and amount + */ + public Cuboid expand(CuboidDirection dir, int amount) { + switch (dir) { + case NORTH: + return new Cuboid(worldName, x1 - amount, y1, z1, x2, y2, z2); + case SOUTH: + return new Cuboid(worldName, x1, y1, z1, x2 + amount, y2, z2); + case EASY: + return new Cuboid(worldName, x1, y1, z1 - amount, x2, y2, z2); + case WEST: + return new Cuboid(worldName, x1, y1, z1, x2, y2, z2 + amount); + case DOWN: + return new Cuboid(worldName, x1, y1 - amount, z1, x2, y2, z2); + case UP: + return new Cuboid(worldName, x1, y1, z1, x2, y2 + amount, z2); + default: + throw new IllegalArgumentException("invalid direction " + dir); + } + } + + /** + * Shift the Cuboid in the given direction by the given amount. + * + * @param dir the direction in which to shift + * @param amount the number of blocks by which to shift + * + * @return a new Cuboid shifted by the given direction and amount + */ + public Cuboid shift(CuboidDirection dir, int amount) { + return expand(dir, amount).expand(dir.opposite(), -amount); + } + + /** + * Outset (grow) the Cuboid in the given direction by the given amount. + * + * @param dir the direction in which to outset (must be HORIZONTAL, VERTICAL, or BOTH) + * @param amount the number of blocks by which to outset + * + * @return a new Cuboid outset by the given direction and amount + */ + public Cuboid outset(CuboidDirection dir, int amount) { + Cuboid c; + switch (dir) { + case HORIZONTAL: + c = expand(CuboidDirection.NORTH, amount).expand(CuboidDirection.SOUTH, amount) + .expand(CuboidDirection.EASY, amount) + .expand(CuboidDirection.WEST, amount); + break; + case VERTICAL: + c = expand(CuboidDirection.DOWN, amount).expand(CuboidDirection.UP, amount); + break; + case BOTH: + c = outset(CuboidDirection.HORIZONTAL, amount).outset(CuboidDirection.VERTICAL, amount); + break; + default: + throw new IllegalArgumentException("invalid direction " + dir); + } + return c; + } + + /** + * Inset (shrink) the Cuboid in the given direction by the given amount. Equivalent to calling outset() with a + * negative amount. + * + * @param dir the direction in which to inset (must be HORIZONTAL, VERTICAL, or BOTH) + * @param amount the number of blocks by which to inset + * + * @return a new Cuboid inset by the given direction and amount + */ + public Cuboid inset(CuboidDirection dir, int amount) { + return outset(dir, -amount); + } + + /** + * Return true if the point at (x,y,z) is contained within this Cuboid. + * + * @param x the X coord + * @param y the Y coord + * @param z the Z coord + * + * @return true if the given point is within this Cuboid, false otherwise + */ + public boolean contains(int x, int y, int z) { + return x >= x1 && x <= x2 && y >= y1 && y <= y2 && z >= z1 && z <= z2; + } + + /** + * Return true if the point at (x,z) is contained within this Cuboid. + * + * @param x the X coord + * @param z the Z coord + * + * @return true if the given point is within this Cuboid, false otherwise + */ + public boolean contains(int x, int z) { + return x >= x1 && x <= x2 && z >= z1 && z <= z2; + } + + /** + * Check if the given Location is contained within this Cuboid. + * + * @param l the Location to check for + * + * @return true if the Location is within this Cuboid, false otherwise + */ + public boolean contains(Location l) { + if (!worldName.equals(l.getWorld().getName())) { + return false; + } + return contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + + /** + * Check if the given Block is contained within this Cuboid. + * + * @param b the Block to check for + * + * @return true if the Block is within this Cuboid, false otherwise + */ + public boolean contains(Block b) { + return contains(b.getLocation()); + } + + /** + * Get the volume of this Cuboid. + * + * @return the Cuboid volume, in blocks + */ + public int volume() { + return getSizeX() * getSizeY() * getSizeZ(); + } + + /** + * Get the Cuboid representing the face of this Cuboid. The resulting Cuboid will be one block thick in the axis + * perpendicular to the requested face. + * + * @param dir which face of the Cuboid to get + * + * @return the Cuboid representing this Cuboid's requested face + */ + public Cuboid getFace(CuboidDirection dir) { + switch (dir) { + case DOWN: + return new Cuboid(worldName, x1, y1, z1, x2, y1, z2); + case UP: + return new Cuboid(worldName, x1, y2, z1, x2, y2, z2); + case NORTH: + return new Cuboid(worldName, x1, y1, z1, x1, y2, z2); + case SOUTH: + return new Cuboid(worldName, x2, y1, z1, x2, y2, z2); + case EASY: + return new Cuboid(worldName, x1, y1, z1, x2, y2, z1); + case WEST: + return new Cuboid(worldName, x1, y1, z2, x2, y2, z2); + default: + throw new IllegalArgumentException("Invalid direction " + dir); + } + } + + /** + * Get the Cuboid big enough to hold both this Cuboid and the given one. + * + * @return a new Cuboid large enough to hold this Cuboid and the given Cuboid + */ + public Cuboid getBoundingCuboid(Cuboid other) { + if (other == null) { + return this; + } + + int xMin = Math.min(getLowerX(), other.getLowerX()); + int yMin = Math.min(getLowerY(), other.getLowerY()); + int zMin = Math.min(getLowerZ(), other.getLowerZ()); + int xMax = Math.max(getUpperX(), other.getUpperX()); + int yMax = Math.max(getUpperY(), other.getUpperY()); + int zMax = Math.max(getUpperZ(), other.getUpperZ()); + + return new Cuboid(worldName, xMin, yMin, zMin, xMax, yMax, zMax); + } + + /** + * Get a block relative to the lower NE point of the Cuboid. + * + * @param x the X coord + * @param y the Y coord + * @param z the Z coord + * + * @return the block at the given position + */ + public Block getRelativeBlock(int x, int y, int z) { + return getWorld().getBlockAt(x1 + x, y1 + y, z1 + z); + } + + /** + * Get a block relative to the lower NE point of the Cuboid in the given World. This version of getRelativeBlock() + * should be used if being called many times, to avoid excessive calls to getWorld(). + * + * @param w the World + * @param x the X coord + * @param y the Y coord + * @param z the Z coord + * + * @return the block at the given position + */ + public Block getRelativeBlock(World w, int x, int y, int z) { + return w.getBlockAt(x1 + x, y1 + y, z1 + z); + } + + /** + * Get a list of the chunks which are fully or partially contained in this cuboid. + * + * @return a list of Chunk objects + */ + public List getChunks() { + List chunks = new ArrayList(); + + World w = getWorld(); + + // These operators get the lower bound of the chunk, by complementing 0xf (15) into 16 + // and using an OR gate on the integer coordinate + + int x1 = getLowerX() & ~0xf; + int x2 = getUpperX() & ~0xf; + int z1 = getLowerZ() & ~0xf; + int z2 = getUpperZ() & ~0xf; + + for (int x = x1; x <= x2; x += 16) { + for (int z = z1; z <= z2; z += 16) { + chunks.add(w.getChunkAt(x >> 4, z >> 4)); + } + } + + return chunks; + } + + /** + * @return horizontal walls of the cuboid + */ + public Cuboid[] getWalls() { + + return new Cuboid[]{ + getFace(CuboidDirection.NORTH), + getFace(CuboidDirection.SOUTH), + getFace(CuboidDirection.WEST), + getFace(CuboidDirection.EASY) + }; + } + + /** + * @return read-only location iterator + */ + public Iterator iterator() { + return new LocationCuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); + } + + @Override + public String toString() { + return "Cuboid: " + worldName + "," + x1 + "," + y1 + "," + z1 + "=>" + x2 + "," + y2 + "," + z2; + } + + public class LocationCuboidIterator implements Iterator { + + private World w; + private int baseX, baseY, baseZ; + private int x, y, z; + private int sizeX, sizeY, sizeZ; + + public LocationCuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { + this.w = w; + baseX = x1; + baseY = y1; + baseZ = z1; + sizeX = Math.abs(x2 - x1) + 1; + sizeY = Math.abs(y2 - y1) + 1; + sizeZ = Math.abs(z2 - z1) + 1; + x = y = z = 0; + } + + public boolean hasNext() { + return x < sizeX && y < sizeY && z < sizeZ; + } + + public Location next() { + Location b = new Location(w, baseX + x, baseY + y, baseZ + z); + if (++x >= sizeX) { + x = 0; + if (++y >= sizeY) { + y = 0; + ++z; + } + } + return b; + } + + public void remove() { + } + } + + +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/cuboid/CuboidDirection.java b/plugin/src/main/java/me/joeleoli/praxi/cuboid/CuboidDirection.java new file mode 100644 index 0000000..71135fa --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/cuboid/CuboidDirection.java @@ -0,0 +1,43 @@ +package me.joeleoli.praxi.cuboid; + +/** + * Represents directions that can be applied to certain faces and actions of a Cuboid + */ +public enum CuboidDirection { + + NORTH, + EASY, + SOUTH, + WEST, + UP, + DOWN, + HORIZONTAL, + VERTICAL, + BOTH, + UNKNOWN; + + public CuboidDirection opposite() { + switch (this) { + case NORTH: + return SOUTH; + case EASY: + return WEST; + case SOUTH: + return NORTH; + case WEST: + return EASY; + case HORIZONTAL: + return VERTICAL; + case VERTICAL: + return HORIZONTAL; + case UP: + return DOWN; + case DOWN: + return UP; + case BOTH: + return BOTH; + default: + return UNKNOWN; + } + } +} \ No newline at end of file diff --git a/plugin/src/main/java/me/joeleoli/praxi/duel/DuelProcedure.java b/plugin/src/main/java/me/joeleoli/praxi/duel/DuelProcedure.java new file mode 100644 index 0000000..71e4bad --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/duel/DuelProcedure.java @@ -0,0 +1,57 @@ +package me.joeleoli.praxi.duel; + +import java.text.MessageFormat; +import lombok.Data; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PraxiPlayer; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.entity.Player; + +@Data +public class DuelProcedure { + + private static final HoverEvent ACCEPT_HOVER = new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new ChatComponentBuilder(Style.YELLOW + "Click to accept this duel invite.").create() + ); + + private Player sender; + private Player target; + private Ladder ladder; + private Arena arena; + + public void send() { + if (!this.sender.isOnline() || !this.target.isOnline()) { + return; + } + + final DuelRequest request = new DuelRequest(this.sender.getUniqueId()); + + request.setLadder(this.ladder); + request.setArena(this.arena); + + final PraxiPlayer senderData = PraxiPlayer.getByUuid(this.sender.getUniqueId()); + + senderData.setDuelProcedure(null); + senderData.getSentDuelRequests().put(this.target.getUniqueId(), request); + + final ClickEvent click = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/duel accept " + this.sender.getName()); + final String ladderContext = StringUtils.startsWithIgnoreCase(this.ladder.getName(), "u") ? "an " : "a "; + + this.sender.sendMessage(Style.translate(new MessageFormat("&eYou sent a duel request to &d{0} &eon arena &d{1}&e.") + .format(new Object[]{ this.target.getName(), this.arena.getName() }))); + this.target.sendMessage(Style.translate( + new MessageFormat("&d{0} &esent you {1} &d{2} &eduel request on arena &d{3}&e.").format(new Object[]{ + this.sender.getName(), ladderContext, this.ladder.getName(), this.arena.getName() + }))); + this.target.sendMessage(new ChatComponentBuilder("") + .parse("&6Click here or type &b/duel accept " + this.sender.getName() + " &6to accept the invite.") + .attachToEachPart(click).attachToEachPart(ACCEPT_HOVER).create()); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/duel/DuelRequest.java b/plugin/src/main/java/me/joeleoli/praxi/duel/DuelRequest.java new file mode 100644 index 0000000..09011ee --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/duel/DuelRequest.java @@ -0,0 +1,24 @@ +package me.joeleoli.praxi.duel; + +import java.util.UUID; +import lombok.Data; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; + +@Data +public class DuelRequest { + + private UUID sender; + private Ladder ladder; + private Arena arena; + private long timestamp = System.currentTimeMillis(); + + public DuelRequest(UUID uuid) { + this.sender = uuid; + } + + public boolean isExpired() { + return System.currentTimeMillis() - this.timestamp >= 30_000; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectArenaMenu.java b/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectArenaMenu.java new file mode 100644 index 0000000..49a1028 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectArenaMenu.java @@ -0,0 +1,94 @@ +package me.joeleoli.praxi.duel.gui; + +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.arena.ArenaType; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class DuelSelectArenaMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.BLUE + Style.BOLD + "Select an arena"; + } + + @Override + public Map getButtons(Player player) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + Map buttons = new HashMap<>(); + + for (Arena arena : Arena.getArenas()) { + if (!arena.isSetup()) { + continue; + } + + if (!arena.getLadders().contains(praxiPlayer.getDuelProcedure().getLadder().getName())) { + continue; + } + + if (praxiPlayer.getDuelProcedure().getLadder().isBuild() && arena.getType() == ArenaType.SHARED) { + continue; + } + + if (praxiPlayer.getDuelProcedure().getLadder().isBuild() && arena.getType() != ArenaType.STANDALONE) { + continue; + } + + if (praxiPlayer.getDuelProcedure().getLadder().isBuild() && arena.isActive()) { + continue; + } + + buttons.put(buttons.size(), new SelectArenaButton(arena)); + } + + return buttons; + } + + @Override + public void onClose(Player player) { + if (!this.isClosedByMenu()) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setDuelProcedure(null); + } + } + + @AllArgsConstructor + private class SelectArenaButton extends Button { + + private Arena arena; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.PAPER).name(Style.GREEN + Style.BOLD + this.arena.getName()).build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + // Update and request the procedure + praxiPlayer.getDuelProcedure().setArena(this.arena); + praxiPlayer.getDuelProcedure().send(); + + // Set closed by menu + Menu.currentlyOpenedMenus.get(player.getName()).setClosedByMenu(true); + + // Force close inventory + player.closeInventory(); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectLadderMenu.java b/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectLadderMenu.java new file mode 100644 index 0000000..40d342a --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/duel/gui/DuelSelectLadderMenu.java @@ -0,0 +1,82 @@ +package me.joeleoli.praxi.duel.gui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class DuelSelectLadderMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.GOLD + Style.BOLD + "Select a ladder"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + for (Ladder ladder : Ladder.getLadders()) { + if (ladder.isEnabled()) { + buttons.put(buttons.size(), new SelectLadderButton(ladder)); + } + } + + return buttons; + } + + @Override + public void onClose(Player player) { + if (!this.isClosedByMenu()) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setDuelProcedure(null); + } + } + + @AllArgsConstructor + private class SelectLadderButton extends Button { + + private Ladder ladder; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(this.ladder.getDisplayIcon()) + .name(Style.PINK + Style.BOLD + this.ladder.getName()) + .lore(Arrays.asList( + "", + Style.YELLOW + "Click here to select " + Style.PINK + Style.BOLD + + this.ladder.getName() + Style.YELLOW + "." + )) + .build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + // Update duel procedure + praxiPlayer.getDuelProcedure().setLadder(this.ladder); + + // Set closed by menu + Menu.currentlyOpenedMenus.get(player.getName()).setClosedByMenu(true); + + // Force close inventory + player.closeInventory(); + + // Open arena selection menu + new DuelSelectArenaMenu().openMenu(player); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/elo/EloUtil.java b/plugin/src/main/java/me/joeleoli/praxi/elo/EloUtil.java new file mode 100644 index 0000000..b955957 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/elo/EloUtil.java @@ -0,0 +1,55 @@ +package me.joeleoli.praxi.elo; + +public class EloUtil { + + private static final KFactor[] K_FACTORS = { + new KFactor(0, 1000, 25), + new KFactor(1001, 1400, 20), + new KFactor(1401, 1800, 15), + new KFactor(1801, 2200, 10) + }; + + private static final int DEFAULT_K_FACTOR = 25; + private static final int WIN = 1; + private static final int LOSS = 0; + + public static int getNewRating(int rating, int opponentRating, boolean won) { + if (won) { + return EloUtil.getNewRating(rating, opponentRating, EloUtil.WIN); + } else { + return EloUtil.getNewRating(rating, opponentRating, EloUtil.LOSS); + } + } + + public static int getNewRating(int rating, int opponentRating, int score) { + double kFactor = EloUtil.getKFactor(rating); + double expectedScore = EloUtil.getExpectedScore(rating, opponentRating); + int newRating = EloUtil.calculateNewRating(rating, score, expectedScore, kFactor); + + if (score == 1) { + if (newRating == rating) { + newRating++; + } + } + return newRating; + } + + private static int calculateNewRating(int oldRating, int score, double expectedScore, double kFactor) { + return oldRating + (int) (kFactor * (score - expectedScore)); + } + + private static double getKFactor(int rating) { + for (int i = 0; i < EloUtil.K_FACTORS.length; i++) { + if (rating >= EloUtil.K_FACTORS[i].getStartIndex() && rating <= EloUtil.K_FACTORS[i].getEndIndex()) { + return EloUtil.K_FACTORS[i].getValue(); + } + } + + return EloUtil.DEFAULT_K_FACTOR; + } + + private static double getExpectedScore(int rating, int opponentRating) { + return 1 / (1 + Math.pow(10, ((double) (opponentRating - rating) / 400))); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/elo/KFactor.java b/plugin/src/main/java/me/joeleoli/praxi/elo/KFactor.java new file mode 100644 index 0000000..00dc09e --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/elo/KFactor.java @@ -0,0 +1,14 @@ +package me.joeleoli.praxi.elo; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class KFactor { + + private final int startIndex; + private final int endIndex; + private final double value; + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/Event.java b/plugin/src/main/java/me/joeleoli/praxi/events/Event.java new file mode 100644 index 0000000..3e54202 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/Event.java @@ -0,0 +1,253 @@ +package me.joeleoli.praxi.events; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.player.PlayerInfo; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.events.task.EventStartTask; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +@Getter +public abstract class Event { + + protected static final String EVENT_PREFIX = Style.GOLD + Style.BOLD + "[Event] " + Style.RESET; + private static final HoverEvent HOVER_EVENT = new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new ChatComponentBuilder("").parse(Style.YELLOW + "Click to join the Sumo event.").create() + ); + private static final ClickEvent CLICK_EVENT = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/event join"); + + private String name; + @Setter + private EventState state = EventState.WAITING; + private EventTask eventTask; + private PlayerInfo host; + private Map eventPlayers = new HashMap<>(); + private int maxPlayers; + @Setter + private Cooldown cooldown; + + public Event(String name, PlayerInfo host, int maxPlayers) { + this.name = name; + this.host = host; + this.maxPlayers = maxPlayers; + } + + public void setEventTask(EventTask task) { + if (this.eventTask != null) { + this.eventTask.cancel(); + } + + this.eventTask = task; + + if (this.eventTask != null) { + this.eventTask.runTaskTimer(Praxi.getInstance(), 0L, 20L); + } + } + + public boolean isWaiting() { + return this.state == EventState.WAITING; + } + + public boolean isFighting() { + return this.state == EventState.ROUND_FIGHTING; + } + + public EventPlayer getEventPlayer(UUID uuid) { + return this.eventPlayers.get(uuid); + } + + public List getPlayers() { + List players = new ArrayList<>(); + + for (EventPlayer eventPlayer : this.eventPlayers.values()) { + final Player player = eventPlayer.toPlayer(); + + if (player != null) { + players.add(player); + } + } + + return players; + } + + public int getRemainingPlayers() { + int remaining = 0; + + for (EventPlayer eventPlayer : this.eventPlayers.values()) { + if (eventPlayer.getState() == EventPlayerState.WAITING) { + remaining++; + } + } + + return remaining; + } + + public void handleStart() { + this.setEventTask(new EventStartTask(this)); + } + + public void handleJoin(Player player) { + this.eventPlayers.put(player.getUniqueId(), new EventPlayer(player)); + this.broadcastMessage(Style.PINK + player.getName() + Style.YELLOW + " joined the event " + Style.PINK + "(" + + this.getRemainingPlayers() + "/" + this.getMaxPlayers() + ")"); + this.onJoin(player); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setEvent(this); + praxiPlayer.setState(PlayerState.IN_EVENT); + praxiPlayer.loadHotbar(); + + player.teleport(Praxi.getInstance().getEventManager().getSumoSpectator()); + } + + public void handleDeath(Player player) { + final EventPlayer loser = this.getEventPlayer(player.getUniqueId()); + + loser.setState(EventPlayerState.ELIMINATED); + + this.onDeath(player); + } + + public void handleLeave(Player player) { + if (this.isFighting(player.getUniqueId())) { + this.handleDeath(player); + } + + this.eventPlayers.remove(player.getUniqueId()); + this.onLeave(player); + + this.getPlayers().forEach(otherPlayer -> { + player.hidePlayer(otherPlayer); + otherPlayer.hidePlayer(player); + }); + + if (this.state == EventState.WAITING) { + this.broadcastMessage(Style.PINK + player.getName() + Style.YELLOW + " left the event " + Style.PINK + + "(" + this.getRemainingPlayers() + "/" + this.getMaxPlayers() + ")"); + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.setEvent(null); + praxiPlayer.loadHotbar(); + + PlayerUtil.spawn(player); + } + + public void end() { + // Remove active event and set cooldown + Praxi.getInstance().getEventManager().setActiveEvent(null); + Praxi.getInstance().getEventManager().setEventCooldown(new Cooldown(60_000L * 3)); + + // Cancel any active task + this.setEventTask(null); + + final Player winner = this.getWinner(); + final List players = this.getPlayers(); + + if (winner == null) { + PlayerUtil.messageAll(EVENT_PREFIX + Style.YELLOW + "The event has been canceled."); + } else { + PlayerUtil.messageAll(EVENT_PREFIX + Style.PINK + winner.getName() + Style.YELLOW + " has won the event!"); + } + + for (EventPlayer eventPlayer : this.eventPlayers.values()) { + final Player player = eventPlayer.toPlayer(); + + if (player != null) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.setEvent(null); + praxiPlayer.loadHotbar(); + + PlayerUtil.spawn(player); + } + } + + players.forEach(player -> players.forEach(otherPlayer -> { + player.hidePlayer(otherPlayer); + otherPlayer.hidePlayer(player); + })); + } + + public boolean canEnd() { + int remaining = 0; + + for (EventPlayer eventPlayer : this.eventPlayers.values()) { + if (eventPlayer.getState() == EventPlayerState.WAITING) { + remaining++; + } + } + + return remaining == 1; + } + + public Player getWinner() { + for (EventPlayer eventPlayer : this.eventPlayers.values()) { + if (eventPlayer.getState() != EventPlayerState.ELIMINATED) { + return eventPlayer.toPlayer(); + } + } + + return null; + } + + public void announce() { + BaseComponent[] components = new ChatComponentBuilder("") + .parse(EVENT_PREFIX + Style.PINK + this.getHost().getName() + Style.YELLOW + " is hosting a " + + Style.PINK + this.getName() + " Event " + Style.GRAY + "[Click to join]") + .attachToEachPart(HOVER_EVENT) + .attachToEachPart(CLICK_EVENT) + .create(); + + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendMessage(components); + } + } + + public void broadcastMessage(String message) { + for (Player player : this.getPlayers()) { + player.sendMessage(EVENT_PREFIX + message); + } + } + + public abstract boolean isSumo(); + + public abstract boolean isCorners(); + + public abstract void onJoin(Player player); + + public abstract void onLeave(Player player); + + public abstract void onRound(); + + public abstract void onDeath(Player player); + + public abstract String getRoundDuration(); + + public abstract EventPlayer getRoundPlayerA(); + + public abstract EventPlayer getRoundPlayerB(); + + public abstract boolean isFighting(UUID uuid); + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/EventManager.java b/plugin/src/main/java/me/joeleoli/praxi/events/EventManager.java new file mode 100644 index 0000000..e61cfb0 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/EventManager.java @@ -0,0 +1,77 @@ +package me.joeleoli.praxi.events; + +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.util.LocationUtil; +import me.joeleoli.praxi.Praxi; +import org.bukkit.Location; + +@Getter +@Setter +public class EventManager { + + private Event activeEvent; + private Cooldown eventCooldown = new Cooldown(0); + private Location sumoSpectator, sumoSpawn1, sumoSpawn2; + private String sumoKbProfile; + + public void setActiveEvent(Event event) { + if (this.activeEvent != null) { + this.activeEvent.setEventTask(null); + } + + if (event == null) { + this.activeEvent = null; + return; + } + + this.activeEvent = event; + this.activeEvent.handleStart(); + this.activeEvent.handleJoin(event.getHost().toPlayer()); + } + + public void load() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getMainConfig(), "event"); + + if (cursor.exists("sumo.spectator")) { + this.sumoSpectator = LocationUtil.deserialize(cursor.getString("sumo.spectator")); + } + + if (cursor.exists("sumo.spawn1")) { + this.sumoSpawn1 = LocationUtil.deserialize(cursor.getString("sumo.spawn1")); + } + + if (cursor.exists("sumo.spawn2")) { + this.sumoSpawn2 = LocationUtil.deserialize(cursor.getString("sumo.spawn2")); + } + + if (cursor.exists("sumo.kb-profile")) { + this.sumoKbProfile = cursor.getString("sumo.kb-profile"); + } + } + + public void save() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getMainConfig(), "event"); + + if (this.sumoSpectator != null) { + cursor.set("sumo.spectator", LocationUtil.serialize(this.sumoSpectator)); + } + + if (this.sumoSpawn1 != null) { + cursor.set("sumo.spawn1", LocationUtil.serialize(this.sumoSpawn1)); + } + + if (this.sumoSpawn2 != null) { + cursor.set("sumo.spawn2", LocationUtil.serialize(this.sumoSpawn2)); + } + + if (this.sumoKbProfile != null) { + cursor.set("sumo.kb-profile", this.sumoKbProfile); + } + + cursor.save(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayer.java b/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayer.java new file mode 100644 index 0000000..a7cf719 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayer.java @@ -0,0 +1,23 @@ +package me.joeleoli.praxi.events; + +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.player.PlayerInfo; +import org.bukkit.entity.Player; + +@Getter +@Setter +public class EventPlayer extends PlayerInfo { + + private EventPlayerState state = EventPlayerState.WAITING; + private int roundWins = 0; + + public EventPlayer(Player player) { + super(player.getUniqueId(), player.getName()); + } + + public void incrementRoundWins() { + this.roundWins++; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayerState.java b/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayerState.java new file mode 100644 index 0000000..1670f51 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/EventPlayerState.java @@ -0,0 +1,15 @@ +package me.joeleoli.praxi.events; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum EventPlayerState { + + WAITING("Waiting"), + ELIMINATED("Eliminated"); + + private String readable; + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/EventState.java b/plugin/src/main/java/me/joeleoli/praxi/events/EventState.java new file mode 100644 index 0000000..9a1aef7 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/EventState.java @@ -0,0 +1,10 @@ +package me.joeleoli.praxi.events; + +public enum EventState { + + WAITING, + ROUND_STARTING, + ROUND_FIGHTING, + ROUND_ENDING, + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/EventTask.java b/plugin/src/main/java/me/joeleoli/praxi/events/EventTask.java new file mode 100644 index 0000000..170fe78 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/EventTask.java @@ -0,0 +1,37 @@ +package me.joeleoli.praxi.events; + +import lombok.Getter; +import me.joeleoli.praxi.Praxi; +import org.bukkit.scheduler.BukkitRunnable; + +@Getter +public abstract class EventTask extends BukkitRunnable { + + private int ticks; + private Event event; + private EventState eventState; + + public EventTask(Event event, EventState eventState) { + this.event = event; + this.eventState = eventState; + } + + @Override + public void run() { + if (Praxi.getInstance().getEventManager().getActiveEvent() == null || !Praxi.getInstance().getEventManager().getActiveEvent().equals(this.event) || this.event.getState() != this.eventState) { + this.cancel(); + return; + } + + this.onRun(); + + this.ticks++; + } + + public int getSeconds() { + return 3 - this.ticks; + } + + public abstract void onRun(); + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/impl/SumoEvent.java b/plugin/src/main/java/me/joeleoli/praxi/events/impl/SumoEvent.java new file mode 100644 index 0000000..6687c3b --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/impl/SumoEvent.java @@ -0,0 +1,165 @@ +package me.joeleoli.praxi.events.impl; + +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.player.PlayerInfo; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TimeUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventPlayer; +import me.joeleoli.praxi.events.EventPlayerState; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.events.task.EventRoundEndTask; +import me.joeleoli.praxi.events.task.EventRoundStartTask; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; + +@Getter +public class SumoEvent extends Event { + + private EventPlayer roundPlayerA; + private EventPlayer roundPlayerB; + @Setter + private long roundStart; + + public SumoEvent(Player player) { + super("Sumo", new PlayerInfo(player), 100); + } + + @Override + public boolean isSumo() { + return true; + } + + @Override + public boolean isCorners() { + return false; + } + + @Override + public void onJoin(Player player) { + this.getPlayers().forEach(otherPlayer -> { + player.showPlayer(otherPlayer); + otherPlayer.showPlayer(player); + }); + } + + @Override + public void onLeave(Player player) { + player.setKnockbackProfile(null); + } + + @Override + public void onRound() { + this.setState(EventState.ROUND_STARTING); + + if (this.roundPlayerA != null) { + final Player player = this.roundPlayerA.toPlayer(); + + if (player != null) { + player.teleport(Praxi.getInstance().getEventManager().getSumoSpectator()); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInEvent()) { + praxiPlayer.loadHotbar(); + } + } + + this.roundPlayerA = null; + } + + if (this.roundPlayerB != null) { + final Player player = this.roundPlayerB.toPlayer(); + + if (player != null) { + player.teleport(Praxi.getInstance().getEventManager().getSumoSpectator()); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInEvent()) { + praxiPlayer.loadHotbar(); + } + } + + this.roundPlayerB = null; + } + + this.roundPlayerA = this.findRoundPlayer(); + this.roundPlayerB = this.findRoundPlayer(); + + final Player playerA = this.roundPlayerA.toPlayer(); + final Player playerB = this.roundPlayerB.toPlayer(); + + PlayerUtil.reset(playerA); + PlayerUtil.reset(playerB); + + PlayerUtil.denyMovement(playerA); + PlayerUtil.denyMovement(playerB); + + playerA.teleport(Praxi.getInstance().getEventManager().getSumoSpawn1()); + playerB.teleport(Praxi.getInstance().getEventManager().getSumoSpawn2()); + + this.setEventTask(new EventRoundStartTask(this)); + } + + @Override + public void onDeath(Player player) { + final EventPlayer winner = this.roundPlayerA.getUuid().equals(player.getUniqueId()) ? this.roundPlayerB : this.roundPlayerA; + + winner.setState(EventPlayerState.WAITING); + winner.incrementRoundWins(); + + this.broadcastMessage(Style.PINK + player.getName() + Style.YELLOW + " was eliminated by " + Style.PINK + winner.getName() + Style.YELLOW + "!"); + this.setState(EventState.ROUND_ENDING); + this.setEventTask(new EventRoundEndTask(this)); + } + + @Override + public String getRoundDuration() { + if (this.getState() == EventState.ROUND_STARTING) { + return "00:00"; + } else if (this.getState() == EventState.ROUND_FIGHTING) { + return TimeUtil.millisToTimer(System.currentTimeMillis() - this.roundStart); + } else { + return "Ending"; + } + } + + @Override + public boolean isFighting(UUID uuid) { + return (this.roundPlayerA != null && this.roundPlayerA.getUuid().equals(uuid)) || (this.roundPlayerB != null && this.roundPlayerB.getUuid().equals(uuid)); + } + + private EventPlayer findRoundPlayer() { + EventPlayer eventPlayer = null; + + for (EventPlayer check : this.getEventPlayers().values()) { + if (!this.isFighting(check.getUuid()) && check.getState() == EventPlayerState.WAITING) { + if (eventPlayer == null) { + eventPlayer = check; + continue; + } + + if (check.getRoundWins() == 0) { + eventPlayer = check; + continue; + } + + if (check.getRoundWins() <= eventPlayer.getRoundWins()) { + eventPlayer = check; + } + } + } + + if (eventPlayer == null) { + throw new RuntimeException("Could not find a new round player"); + } + + return eventPlayer; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundEndTask.java b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundEndTask.java new file mode 100644 index 0000000..2f314c2 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundEndTask.java @@ -0,0 +1,24 @@ +package me.joeleoli.praxi.events.task; + +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.events.EventTask; + +public class EventRoundEndTask extends EventTask { + + public EventRoundEndTask(Event event) { + super(event, EventState.ROUND_ENDING); + } + + @Override + public void onRun() { + if (this.getTicks() >= 3) { + if (this.getEvent().canEnd()) { + this.getEvent().end(); + } else { + this.getEvent().onRound(); + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundStartTask.java b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundStartTask.java new file mode 100644 index 0000000..3b9920b --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventRoundStartTask.java @@ -0,0 +1,39 @@ +package me.joeleoli.praxi.events.task; + +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.events.EventTask; +import me.joeleoli.praxi.events.impl.SumoEvent; +import org.bukkit.entity.Player; + +public class EventRoundStartTask extends EventTask { + + public EventRoundStartTask(Event event) { + super(event, EventState.ROUND_STARTING); + } + + @Override + public void onRun() { + if (this.getTicks() >= 3) { + this.getEvent().setEventTask(null); + this.getEvent().setState(EventState.ROUND_FIGHTING); + + final Player playerA = this.getEvent().getRoundPlayerA().toPlayer(); + final Player playerB = this.getEvent().getRoundPlayerB().toPlayer(); + + PlayerUtil.allowMovement(playerA); + PlayerUtil.allowMovement(playerB); + + ((SumoEvent) this.getEvent()).setRoundStart(System.currentTimeMillis()); + } else { + final int seconds = this.getSeconds(); + + this.getEvent().broadcastMessage( + Style.YELLOW + "The round will start in " + Style.PINK + (seconds) + " second" + + (seconds == 1 ? "" : "s") + Style.YELLOW + "..."); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/events/task/EventStartTask.java b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventStartTask.java new file mode 100644 index 0000000..a8531a7 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/events/task/EventStartTask.java @@ -0,0 +1,45 @@ +package me.joeleoli.praxi.events.task; + +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.events.EventTask; + +public class EventStartTask extends EventTask { + + public EventStartTask(Event event) { + super(event, EventState.WAITING); + } + + @Override + public void onRun() { + if (this.getTicks() >= 600) { + this.getEvent().end(); + return; + } + + if (this.getEvent().getPlayers().size() <= 1 && this.getEvent().getCooldown() != null) { + this.getEvent().setCooldown(null); + this.getEvent().broadcastMessage(Style.YELLOW + "There are not enough players for the event to start."); + } + + if (this.getEvent().getPlayers().size() == this.getEvent().getMaxPlayers() || (this.getTicks() >= 30 && this.getEvent().getPlayers().size() >= 2)) { + if (this.getEvent().getCooldown() == null) { + this.getEvent().setCooldown(new Cooldown(11_000)); + this.getEvent().broadcastMessage(Style.YELLOW + "The event will start in " + Style.PINK + "10 seconds" + Style.YELLOW + "..."); + } else { + if (this.getEvent().getCooldown().hasExpired()) { + this.getEvent().setState(EventState.ROUND_STARTING); + this.getEvent().onRound(); + this.getEvent().setEventTask(new EventRoundStartTask(this.getEvent())); + } + } + } + + if (this.getTicks() % 10 == 0) { + this.getEvent().announce(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/handler/PlayerMovementHandler.java b/plugin/src/main/java/me/joeleoli/praxi/handler/PlayerMovementHandler.java new file mode 100644 index 0000000..03b2f34 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/handler/PlayerMovementHandler.java @@ -0,0 +1,67 @@ +package me.joeleoli.praxi.handler; + +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.events.EventState; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.ragespigot.handler.MovementHandler; +import net.minecraft.server.v1_8_R3.PacketPlayInFlying; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +public class PlayerMovementHandler implements MovementHandler { + + @Override + public void handleUpdateLocation(Player player, Location from, Location to, PacketPlayInFlying packetPlayInFlying) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInLobby() || praxiPlayer.isInQueue()) { + if (player.getGameMode() != GameMode.CREATIVE) { + if (to.getX() >= 200 || to.getX() <= -200 || to.getZ() >= 200 || to.getZ() <= -200) { + PlayerUtil.spawn(player); + } + } + } else if (praxiPlayer.isInMatch()) { + if (praxiPlayer.getMatch().getLadder().isSumo() || praxiPlayer.getMatch().getLadder().isSpleef()) { + final Match match = praxiPlayer.getMatch(); + + if (match.isFighting()) { + if (player.getLocation().getBlock().getType() == Material.WATER || + player.getLocation().getBlock().getType() == Material.STATIONARY_WATER) { + Player killer = player.getKiller(); + + if (killer == null) { + if (match.isSoloMatch()) { + killer = praxiPlayer.getMatch().getOpponentPlayer(player); + } + } + + match.handleDeath(player, killer, false); + } + } + } + } else if (praxiPlayer.isInEvent()) { + final Event event = praxiPlayer.getEvent(); + + if (event.isSumo()) { + if (event.getState() == EventState.ROUND_FIGHTING) { + if (event.isFighting(player.getUniqueId())) { + if (player.getLocation().getBlock().getType() == Material.WATER || + player.getLocation().getBlock().getType() == Material.STATIONARY_WATER) { + event.handleDeath(player); + } + } + } + } + } + } + + @Override + public void handleUpdateRotation(Player player, Location from, Location to, PacketPlayInFlying packetPlayInFlying) { + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/Kit.java b/plugin/src/main/java/me/joeleoli/praxi/kit/Kit.java new file mode 100644 index 0000000..5cd942a --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/Kit.java @@ -0,0 +1,27 @@ +package me.joeleoli.praxi.kit; + +import lombok.Data; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Data +public class Kit { + + public static final ItemStack DEFAULT_KIT = new ItemBuilder(Material.BOOK).name(Style.GOLD + "Default Kit").build(); + + private ItemStack[] armor; + private ItemStack[] contents; + + public Kit() { + this.armor = new ItemStack[4]; + this.contents = new ItemStack[36]; + } + + public Kit(ItemStack[] armor, ItemStack[] contents) { + this.armor = armor; + this.contents = contents; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/NamedKit.java b/plugin/src/main/java/me/joeleoli/praxi/kit/NamedKit.java new file mode 100644 index 0000000..6930c48 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/NamedKit.java @@ -0,0 +1,12 @@ +package me.joeleoli.praxi.kit; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class NamedKit extends Kit { + + private String name; + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitEditorMenu.java b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitEditorMenu.java new file mode 100644 index 0000000..a1f3731 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitEditorMenu.java @@ -0,0 +1,280 @@ +package me.joeleoli.praxi.kit.gui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.menu.buttons.DisplayButton; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.ItemUtil; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.praxi.kit.NamedKit; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class KitEditorMenu extends Menu { + + private static final int[] ITEM_POSITIONS = new int[]{ + 20, 21, 22, 23, 24, 25, 26, 29, 30, 31, 32, 33, 34, 35, 38, 39, 40, 41, 42, 43, 44, 47, 48, 49, 50, 51, 52, + 53 + }; + private static final int[] BORDER_POSITIONS = new int[]{ 1, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 28, 37, 46 }; + private static final Button BORDER_BUTTON = Button.placeholder(Material.COAL_BLOCK, (byte) 0, " "); + + public KitEditorMenu() { + this.setUpdateAfterClick(false); + } + + @Override + public String getTitle(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + return Style.GOLD + "Editing " + Style.AQUA + praxiPlayer.getKitEditor().getSelectedKit().getName(); + } + + @Override + public Map getButtons(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + NamedKit kit = praxiPlayer.getKitEditor().getSelectedKit(); + Map buttons = new HashMap<>(); + + for (int border : BORDER_POSITIONS) { + buttons.put(border, BORDER_BUTTON); + } + + buttons.put(0, new CurrentKitButton()); + buttons.put(2, new SaveButton()); + buttons.put(6, new LoadDefaultKitButton()); + buttons.put(7, new ClearInventoryButton()); + buttons.put(8, new CancelButton()); + buttons.put(18, new ArmorDisplayButton(kit.getArmor()[3])); + buttons.put(27, new ArmorDisplayButton(kit.getArmor()[2])); + buttons.put(36, new ArmorDisplayButton(kit.getArmor()[1])); + buttons.put(45, new ArmorDisplayButton(kit.getArmor()[0])); + + List items = praxiPlayer.getKitEditor().getSelectedLadder().getKitEditorItems(); + + for (int i = 20; i < (praxiPlayer.getKitEditor().getSelectedLadder().getKitEditorItems().size() + 20); i++) { + buttons.put(ITEM_POSITIONS[i - 20], new InfiniteItemButton(items.get(i - 20))); + } + + return buttons; + } + + @Override + public void onOpen(Player player) { + if (!this.isClosedByMenu()) { + PlayerUtil.reset(player); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + praxiPlayer.getKitEditor().setActive(true); + + if (praxiPlayer.getKitEditor().getSelectedKit() != null) { + player.getInventory().setContents(praxiPlayer.getKitEditor().getSelectedKit().getContents()); + } + + player.updateInventory(); + } + } + + @Override + public void onClose(Player player) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + praxiPlayer.getKitEditor().setActive(false); + + if (!praxiPlayer.isInMatch()) { + TaskUtil.runLater(praxiPlayer::loadHotbar, 1L); + } + } + + @AllArgsConstructor + private class ArmorDisplayButton extends Button { + + private ItemStack itemStack; + + @Override + public ItemStack getButtonItem(Player player) { + if (this.itemStack == null || this.itemStack.getType() == Material.AIR) { + return new ItemStack(Material.AIR); + } + + return new ItemBuilder(this.itemStack.clone()) + .name(Style.AQUA + ItemUtil.getName(this.itemStack)) + .lore(Arrays.asList( + "", + Style.YELLOW + "This is automatically equipped." + )) + .build(); + } + + } + + @AllArgsConstructor + private class CurrentKitButton extends Button { + + @Override + public ItemStack getButtonItem(Player player) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + return new ItemBuilder(Material.NAME_TAG) + .name(Style.GREEN + Style.BOLD + "Editing: " + Style.AQUA + + praxiPlayer.getKitEditor().getSelectedKit().getName()) + .build(); + } + + } + + @AllArgsConstructor + private class ClearInventoryButton extends Button { + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.STAINED_CLAY) + .durability(7) + .name(Style.YELLOW + Style.BOLD + "Clear Inventory") + .lore(Arrays.asList( + "", + Style.YELLOW + "This will clear your inventory", + Style.YELLOW + "so you can start over." + )) + .build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + Button.playNeutral(player); + player.getInventory().setContents(new ItemStack[36]); + player.updateInventory(); + } + + @Override + public boolean shouldUpdate(Player player, int i, ClickType clickType) { + return true; + } + + } + + @AllArgsConstructor + private class LoadDefaultKitButton extends Button { + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.STAINED_CLAY) + .durability(7) + .name(Style.YELLOW + Style.BOLD + "Load default kit") + .lore(Arrays.asList( + "", + Style.YELLOW + "Click this to load the default kit", + Style.YELLOW + "into the kit editing menu." + )) + .build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + Button.playNeutral(player); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + player.getInventory() + .setContents(praxiPlayer.getKitEditor().getSelectedLadder().getDefaultKit().getContents()); + player.updateInventory(); + } + + @Override + public boolean shouldUpdate(Player player, int i, ClickType clickType) { + return true; + } + + } + + @AllArgsConstructor + private class SaveButton extends Button { + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.STAINED_CLAY) + .durability(5) + .name(Style.GREEN + Style.BOLD + "Save") + .lore(Arrays.asList( + "", + Style.YELLOW + "Click this to save your kit." + )) + .build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + Button.playNeutral(player); + player.closeInventory(); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getKitEditor().getSelectedKit() != null) { + praxiPlayer.getKitEditor().getSelectedKit().setContents(player.getInventory().getContents()); + } + + praxiPlayer.loadHotbar(); + + new KitManagementMenu(praxiPlayer.getKitEditor().getSelectedLadder()).openMenu(player); + } + + } + + @AllArgsConstructor + private class CancelButton extends Button { + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.STAINED_CLAY) + .durability(14) + .name(Style.RED + Style.BOLD + "Cancel") + .lore(Arrays.asList( + "", + Style.YELLOW + "Click this to abort editing your kit,", + Style.YELLOW + "and return to the kit menu." + )) + .build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + Button.playNeutral(player); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getKitEditor().getSelectedLadder() != null) { + new KitManagementMenu(praxiPlayer.getKitEditor().getSelectedLadder()).openMenu(player); + } + } + + } + + private class InfiniteItemButton extends DisplayButton { + + InfiniteItemButton(ItemStack itemStack) { + super(itemStack, false); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + final Inventory inventory = player.getOpenInventory().getTopInventory(); + final ItemStack itemStack = inventory.getItem(i); + + inventory.setItem(i, itemStack); + + player.setItemOnCursor(itemStack); + player.updateInventory(); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitManagementMenu.java b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitManagementMenu.java new file mode 100644 index 0000000..ba09789 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/KitManagementMenu.java @@ -0,0 +1,239 @@ +package me.joeleoli.praxi.kit.gui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.kit.NamedKit; +import me.joeleoli.praxi.kit.gui.button.BackButton; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class KitManagementMenu extends Menu { + + private static final Button PLACEHOLDER = Button.placeholder(Material.STAINED_GLASS_PANE, (byte) 7, " "); + + private Ladder ladder; + + public KitManagementMenu(Ladder ladder) { + this.ladder = ladder; + + this.setPlaceholder(true); + this.setUpdateAfterClick(false); + } + + @Override + public String getTitle(Player player) { + return Style.GOLD + "Viewing " + this.ladder.getName() + " kits"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + NamedKit[] kits = praxiPlayer.getKits(this.ladder); + + if (kits == null) { + return buttons; + } + + int startPos = -1; + + for (int i = 0; i < 4; i++) { + NamedKit kit = kits[i]; + startPos += 2; + + buttons.put(startPos, kit == null ? new CreateKitButton(i) : new KitDisplayButton(kit)); + buttons.put(startPos + 18, new LoadKitButton(i)); + buttons.put(startPos + 27, kit == null ? PLACEHOLDER : new RenameKitButton(kit)); + buttons.put(startPos + 36, kit == null ? PLACEHOLDER : new DeleteKitButton(kit)); + } + + buttons.put(36, new BackButton(new SelectLadderKitMenu())); + + return buttons; + } + + @Override + public void onClose(Player player) { + if (!this.isClosedByMenu()) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setState(praxiPlayer.getKitEditor().getPreviousState()); + praxiPlayer.getKitEditor().setSelectedLadder(null); + } + } + + @AllArgsConstructor + private class DeleteKitButton extends Button { + + private NamedKit kit; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.STAINED_CLAY) + .name(Style.RED + Style.BOLD + "Delete") + .durability(14) + .lore(Arrays.asList( + "", + Style.RED + "Click to delete this kit.", + Style.RED + "You will " + Style.BOLD + "NOT" + Style.RED + " be able to", + Style.RED + "recover this kit." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.deleteKit(praxiPlayer.getKitEditor().getSelectedLadder(), this.kit); + + new KitManagementMenu(praxiPlayer.getKitEditor().getSelectedLadder()).openMenu(player); + } + + } + + @AllArgsConstructor + private class CreateKitButton extends Button { + + private int index; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.IRON_SWORD) + .name(Style.GREEN + Style.BOLD + "Create Kit") + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final Ladder ladder = praxiPlayer.getKitEditor().getSelectedLadder(); + + // TODO: this shouldn't be null but sometimes it is? + if (ladder == null) { + player.closeInventory(); + return; + } + + final NamedKit kit = new NamedKit("Kit " + (this.index + 1)); + + if (ladder.getDefaultKit() != null) { + if (ladder.getDefaultKit().getArmor() != null) { + kit.setArmor(ladder.getDefaultKit().getArmor()); + } + + if (ladder.getDefaultKit().getContents() != null) { + kit.setContents(ladder.getDefaultKit().getContents()); + } + } + + praxiPlayer.replaceKit(ladder, this.index, kit); + praxiPlayer.getKitEditor().setSelectedKit(kit); + + new KitEditorMenu().openMenu(player); + } + + } + + @AllArgsConstructor + private class RenameKitButton extends Button { + + private NamedKit kit; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.SIGN) + .name(Style.YELLOW + Style.BOLD + "Rename") + .lore(Arrays.asList( + "", + Style.YELLOW + "Click to rename this kit." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + Menu.currentlyOpenedMenus.get(player.getName()).setClosedByMenu(true); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.getKitEditor().setActive(true); + praxiPlayer.getKitEditor().setRename(true); + praxiPlayer.getKitEditor().setSelectedKit(this.kit); + + player.closeInventory(); + player.sendMessage( + Style.YELLOW + "Renaming " + Style.BOLD + this.kit.getName() + Style.YELLOW + "... " + Style.GREEN + + "Enter the new name now."); + } + + } + + @AllArgsConstructor + private class LoadKitButton extends Button { + + private int index; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.BOOK) + .name(Style.GREEN + Style.BOLD + "Load/Edit") + .lore(Arrays.asList( + "", + Style.YELLOW + "Click to edit this kit." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + // TODO: this shouldn't be null but sometimes it is? + if (praxiPlayer.getKitEditor().getSelectedLadder() == null) { + player.closeInventory(); + return; + } + + NamedKit kit = praxiPlayer.getKit(praxiPlayer.getKitEditor().getSelectedLadder(), this.index); + + if (kit == null) { + kit = new NamedKit("Kit " + (this.index + 1)); + kit.setArmor(praxiPlayer.getKitEditor().getSelectedLadder().getDefaultKit().getArmor()); + kit.setContents(praxiPlayer.getKitEditor().getSelectedLadder().getDefaultKit().getContents()); + + praxiPlayer.replaceKit(praxiPlayer.getKitEditor().getSelectedLadder(), this.index, kit); + } + + praxiPlayer.getKitEditor().setSelectedKit(kit); + + new KitEditorMenu().openMenu(player); + } + + } + + @AllArgsConstructor + private class KitDisplayButton extends Button { + + private NamedKit kit; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.BOOK) + .name(Style.GREEN + Style.BOLD + this.kit.getName()) + .build(); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/gui/SelectLadderKitMenu.java b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/SelectLadderKitMenu.java new file mode 100644 index 0000000..0071da5 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/SelectLadderKitMenu.java @@ -0,0 +1,67 @@ +package me.joeleoli.praxi.kit.gui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class SelectLadderKitMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.GOLD + Style.BOLD + "Select a ladder"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + Ladder.getLadders().forEach(ladder -> { + if (ladder.isEnabled()) { + buttons.put(buttons.size(), new LadderKitDisplayButton(ladder)); + } + }); + + return buttons; + } + + @AllArgsConstructor + private class LadderKitDisplayButton extends Button { + + private Ladder ladder; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(this.ladder.getDisplayIcon()) + .name(Style.PINK + Style.BOLD + this.ladder.getName()) + .lore(Arrays.asList( + "", + Style.YELLOW + "Click to select " + Style.PINK + Style.BOLD + this.ladder.getName() + + Style.YELLOW + "." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + player.closeInventory(); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.getKitEditor().setSelectedLadder(this.ladder); + praxiPlayer.getKitEditor().setPreviousState(praxiPlayer.getState()); + + new KitManagementMenu(this.ladder).openMenu(player); + } + + } +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/kit/gui/button/BackButton.java b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/button/BackButton.java new file mode 100644 index 0000000..b6811b2 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/kit/gui/button/BackButton.java @@ -0,0 +1,33 @@ +package me.joeleoli.praxi.kit.gui.button; + +import java.util.Arrays; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + + +@AllArgsConstructor +public class BackButton extends Button { + + private Menu back; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.REDSTONE).name(Style.RED + Style.BOLD + "Back").lore(Arrays + .asList(Style.RED + "Click here to return to", Style.RED + "the previous menu.")).build(); + } + + @Override + public void clicked(Player player, int i, ClickType clickType, int hb) { + Button.playNeutral(player); + + this.back.openMenu(player); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/ladder/Ladder.java b/plugin/src/main/java/me/joeleoli/praxi/ladder/Ladder.java new file mode 100644 index 0000000..a2cc1dd --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/ladder/Ladder.java @@ -0,0 +1,86 @@ +package me.joeleoli.praxi.ladder; + +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import lombok.Getter; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.nucleus.util.InventoryUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.kit.Kit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +@Data +public class Ladder { + + @Getter + private static List ladders = new ArrayList<>(); + + private String name; + private String displayName; + private ItemStack displayIcon; + private Kit defaultKit = new Kit(); + private List kitEditorItems = new ArrayList<>(); + private boolean enabled, build, sumo, parkour, spleef, regeneration, allowPotionFill; + private int hitDelay = 20; + private String kbProfile; + + public Ladder(String name) { + this.name = name; + this.displayName = ChatColor.AQUA + this.name; + this.displayIcon = new ItemStack(Material.DIAMOND_SWORD); + + ladders.add(this); + } + + public static Ladder getByName(String name) { + for (Ladder ladder : ladders) { + if (ladder.getName().equalsIgnoreCase(name)) { + return ladder; + } + } + + return null; + } + + public ItemStack getDisplayIcon() { + return this.displayIcon.clone(); + } + + public void save() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getLadderConfig(), "ladders." + this.name); + + cursor.set("display-name", this.displayName); + cursor.set("display-icon.material", this.displayIcon.getType().name()); + cursor.set("display-icon.durability", this.displayIcon.getDurability()); + cursor.set("display-icon.amount", this.displayIcon.getAmount()); + cursor.set("enabled", this.enabled); + cursor.set("build", this.build); + cursor.set("sumo", this.sumo); + cursor.set("spleef", this.spleef); + cursor.set("parkour", this.parkour); + cursor.set("regeneration", this.regeneration); + cursor.set("hit-delay", this.hitDelay); + + if (this.displayIcon.hasItemMeta()) { + final ItemMeta itemMeta = this.displayIcon.getItemMeta(); + + if (itemMeta.hasDisplayName()) { + cursor.set("display-icon.name", itemMeta.getDisplayName()); + } + + if (itemMeta.hasLore()) { + cursor.set("display-icon.lore", itemMeta.getLore()); + } + } + + cursor.set("default-kit.armor", InventoryUtil.serializeInventory(this.defaultKit.getArmor())); + cursor.set("default-kit.contents", InventoryUtil.serializeInventory(this.defaultKit.getContents())); + + cursor.save(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/ArenaListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/ArenaListener.java new file mode 100644 index 0000000..f959c33 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/ArenaListener.java @@ -0,0 +1,103 @@ +package me.joeleoli.praxi.listener; + +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.arena.ArenaType; +import me.joeleoli.praxi.arena.selection.Selection; +import me.joeleoli.praxi.match.Match; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + +public class ArenaListener implements Listener { + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (!(event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_BLOCK)) { + return; + } + + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + + Block clicked = event.getClickedBlock(); + int location = 0; + + if (item == null || !item.equals(Selection.SELECTION_WAND)) { + return; + } + + Selection selection = Selection.createOrGetSelection(player); + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + selection.setPoint2(clicked.getLocation()); + location = 2; + } else if (event.getAction() == Action.LEFT_CLICK_BLOCK) { + selection.setPoint1(clicked.getLocation()); + location = 1; + } + + event.setCancelled(true); + event.setUseItemInHand(Event.Result.DENY); + event.setUseInteractedBlock(Event.Result.DENY); + + String message = Style.AQUA + (location == 1 ? "First" : "Second") + + " location " + Style.YELLOW + "(" + Style.GREEN + + clicked.getX() + Style.YELLOW + ", " + Style.GREEN + + clicked.getY() + Style.YELLOW + ", " + Style.GREEN + + clicked.getZ() + Style.YELLOW + ")" + Style.AQUA + " has been set!"; + + if (selection.isFullObject()) { + message += Style.RED + " (" + Style.YELLOW + selection.getCuboid().volume() + Style.AQUA + " blocks" + + Style.RED + ")"; + } + + player.sendMessage(message); + } + + @EventHandler + public void onBlockFromTo(BlockFromToEvent event) { + final int x = event.getBlock().getX(); + final int y = event.getBlock().getY(); + final int z = event.getBlock().getZ(); + + Arena foundArena = null; + + for (Arena arena : Arena.getArenas()) { + if (!(arena.getType() == ArenaType.STANDALONE || arena.getType() == ArenaType.DUPLICATE)) { + continue; + } + + if (!arena.isActive()) { + continue; + } + + if (x >= arena.getX1() && x <= arena.getX2() && y >= arena.getY1() && y <= arena.getY2() && + z >= arena.getZ1() && z <= arena.getZ2()) { + foundArena = arena; + break; + } + } + + if (foundArena == null) { + return; + } + + for (Match match : Match.getMatches()) { + if (match.getArena().equals(foundArena)) { + if (match.isFighting()) { + match.getPlacedBlocks().add(event.getToBlock().getLocation()); + } + + break; + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/EntityListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/EntityListener.java new file mode 100644 index 0000000..4b6fc94 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/EntityListener.java @@ -0,0 +1,178 @@ +package me.joeleoli.praxi.listener; + +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.util.BukkitUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchTeam; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Arrow; +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.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; + +public class EntityListener implements Listener { + + @EventHandler + public void onEntityRegainHealth(EntityRegainHealthEvent event) { + if (event.getEntity() instanceof Player) { + if (event.getRegainReason() == EntityRegainHealthEvent.RegainReason.SATIATED) { + final Player player = (Player) event.getEntity(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInMatch()) { + if (!praxiPlayer.getMatch().getLadder().isRegeneration()) { + event.setCancelled(true); + } + } else { + event.setCancelled(true); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + if (event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInMatch()) { + if (event.getCause() == EntityDamageEvent.DamageCause.VOID) { + praxiPlayer.getMatch().handleDeath(player, null, false); + return; + } + + if (!praxiPlayer.getMatch().isFighting()) { + event.setCancelled(true); + return; + } + + if (praxiPlayer.getMatch().isTeamMatch()) { + if (!praxiPlayer.getMatch().getMatchPlayer(player).isAlive()) { + event.setCancelled(true); + return; + } + } + + if (praxiPlayer.getMatch().getLadder().isSumo() || praxiPlayer.getMatch().getLadder().isSpleef()) { + event.setDamage(0); + player.setHealth(20.0); + player.updateInventory(); + } + } else if (praxiPlayer.isInEvent()) { + if (event.getCause() == EntityDamageEvent.DamageCause.VOID) { + praxiPlayer.getEvent().handleDeath(player); + return; + } + + if (praxiPlayer.getEvent().isSumo()) { + if (!praxiPlayer.getEvent().isFighting() || !praxiPlayer.getEvent().isFighting(player.getUniqueId())) { + event.setCancelled(true); + return; + } + + event.setDamage(0); + player.setHealth(20.0); + player.updateInventory(); + } + } else { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + final Player attacker = BukkitUtil.getDamager(event); + + if (attacker != null && event.getEntity() instanceof Player) { + final Player damaged = (Player) event.getEntity(); + final PraxiPlayer damagedData = PraxiPlayer.getByUuid(damaged.getUniqueId()); + final PraxiPlayer attackerData = PraxiPlayer.getByUuid(attacker.getUniqueId()); + + if (attackerData.isSpectating() || damagedData.isSpectating()) { + event.setCancelled(true); + return; + } + + if (damagedData.isInMatch() && attackerData.isInMatch()) { + final Match match = attackerData.getMatch(); + + + if (!damagedData.getMatch().getMatchId().equals(attackerData.getMatch().getMatchId())) { + event.setCancelled(true); + return; + } + + if (!match.getMatchPlayer(damaged).isAlive() || !match.getMatchPlayer(attacker).isAlive()) { + event.setCancelled(true); + return; + } + + if (match.isSoloMatch()) { + attackerData.getMatch().getMatchPlayer(attacker).handleHit(); + damagedData.getMatch().getMatchPlayer(damaged).resetCombo(); + + if (event.getDamager() instanceof Arrow) { + double health = Math.ceil(damaged.getHealth() - event.getFinalDamage()) / 2.0D; + + attacker.sendMessage(Style.formatArrowHitMessage(damaged.getName(), health)); + } + } else if (match.isTeamMatch()) { + final MatchTeam attackerTeam = match.getTeam(attacker); + final MatchTeam damagedTeam = match.getTeam(damaged); + + if (attackerTeam == null || damagedTeam == null) { + event.setCancelled(true); + } else { + if (attackerTeam.equals(damagedTeam)) { + event.setCancelled(true); + } else { + attackerData.getMatch().getMatchPlayer(attacker).handleHit(); + damagedData.getMatch().getMatchPlayer(damaged).resetCombo(); + + if (event.getDamager() instanceof Arrow) { + double health = Math.ceil(damaged.getHealth() - event.getFinalDamage()) / 2.0D; + + attacker.sendMessage(Style.formatArrowHitMessage(damaged.getName(), health)); + } + } + } + } + } else if (damagedData.isInEvent() && attackerData.isInEvent()) { + final Event praxiEvent = damagedData.getEvent(); + + if (!praxiEvent.isFighting() || !praxiEvent.isFighting(damaged.getUniqueId()) || !praxiEvent.isFighting(attacker.getUniqueId())) { + event.setCancelled(true); + } + } + } + } + + @EventHandler + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntity() instanceof Player) { + final Player player = (Player) event.getEntity(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInMatch() && praxiPlayer.getMatch().isFighting()) { + if (event.getFoodLevel() >= 20) { + event.setFoodLevel(20); + player.setSaturation(20); + } else { + event.setCancelled(Nucleus.RANDOM.nextInt(100) > 25); + } + } else { + event.setCancelled(true); + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/PlayerListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/PlayerListener.java new file mode 100644 index 0000000..15ab3af --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/PlayerListener.java @@ -0,0 +1,624 @@ +package me.joeleoli.praxi.listener; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import me.joeleoli.nucleus.command.CommandHandler; +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.player.gui.ViewPlayerMenu; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.nucleus.util.TimeUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.kit.Kit; +import me.joeleoli.praxi.kit.NamedKit; +import me.joeleoli.praxi.kit.gui.KitManagementMenu; +import me.joeleoli.praxi.kit.gui.SelectLadderKitMenu; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.party.gui.OtherPartiesMenu; +import me.joeleoli.praxi.party.gui.PartyEventSelectEventMenu; +import me.joeleoli.praxi.player.PlayerHotbar; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.queue.Queue; +import me.joeleoli.praxi.queue.gui.QueueJoinMenu; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +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.block.BlockPlaceEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class PlayerListener implements Listener { + + @EventHandler + public void onPlayerItemConsume(PlayerItemConsumeEvent event) { + if (event.getItem().getType() == Material.GOLDEN_APPLE) { + if (event.getItem().hasItemMeta() && + event.getItem().getItemMeta().getDisplayName().contains("Golden Head")) { + final Player player = event.getPlayer(); + + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 200, 1)); + player.addPotionEffect(new PotionEffect(PotionEffectType.ABSORPTION, 2400, 0)); + player.setFoodLevel(Math.min(player.getFoodLevel() + 6, 20)); + } + } + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + final Player player = event.getPlayer(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isSpectating() && event.getRightClicked() instanceof Player && player.getItemInHand() != null) { + final Player target = (Player) event.getRightClicked(); + + if (PlayerHotbar.fromItemStack(player.getItemInHand()) == PlayerHotbar.HotbarItem.VIEW_INVENTORY) { + new ViewPlayerMenu(target).openMenu(player); + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + public void onAsyncPlayerChat(AsyncPlayerChatEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (event.getMessage().startsWith("@") || event.getMessage().startsWith("!")) { + if (praxiPlayer.getParty() != null) { + event.setCancelled(true); + praxiPlayer.getParty().broadcast( + Style.GOLD + "[Party]" + Style.RESET + " " + event.getPlayer().getDisplayName() + Style.RESET + + ": " + Style.strip(event.getMessage().substring(1))); + return; + } + } + + if (praxiPlayer.getKitEditor().isRenaming()) { + event.setCancelled(true); + + if (event.getMessage().length() > 16) { + event.getPlayer().sendMessage(Style.RED + "A kit name cannot be more than 16 characters long."); + return; + } + + if (!praxiPlayer.isInMatch()) { + new KitManagementMenu(praxiPlayer.getKitEditor().getSelectedLadder()).openMenu(event.getPlayer()); + } + + praxiPlayer.getKitEditor().getSelectedKit().setName(event.getMessage()); + praxiPlayer.getKitEditor().setActive(false); + praxiPlayer.getKitEditor().setRename(false); + praxiPlayer.getKitEditor().setSelectedKit(null); + } + } + + @EventHandler + public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) { + PraxiPlayer praxiPlayer = new PraxiPlayer(event.getUniqueId(), null); + + praxiPlayer.setName(event.getName()); + praxiPlayer.load(); + + if (!praxiPlayer.isLoaded()) { + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); + event.setKickMessage(ChatColor.RED + "Failed to load your profile. Try again later."); + return; + } + + PraxiPlayer.getPlayers().put(event.getUniqueId(), praxiPlayer); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + event.setJoinMessage(null); + + event.getPlayer().sendMessage(new String[]{ + Style.getBorderLine(), + "", + Style.center(Style.YELLOW + "Welcome to " + Style.PINK + Style.BOLD + "MineXD Practice" + + Style.YELLOW + "!"), + "", + Style.center(Style.YELLOW + "Follow our twitter " + Style.PINK + "@MineXD" + Style.YELLOW + + " for updates and giveaways."), + "", + Style.getBorderLine() + }); + + PlayerUtil.spawn(event.getPlayer()); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + praxiPlayer.loadHotbar(); + + Bukkit.getOnlinePlayers().forEach(player -> { + player.hidePlayer(event.getPlayer()); + event.getPlayer().hidePlayer(player); + }); + } + + @EventHandler + public void onPlayerKick(PlayerKickEvent event) { + if (event.getReason() != null) { + if (event.getReason().contains("Flying is not enabled")) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerQuit(PlayerQuitEvent event) { + event.setQuitMessage(null); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getPlayers().remove(event.getPlayer().getUniqueId()); + + if (praxiPlayer != null) { + if (praxiPlayer.getParty() != null) { + if (praxiPlayer.getParty().isLeader(event.getPlayer().getUniqueId())) { + praxiPlayer.getParty().disband(); + } else { + praxiPlayer.getParty().leave(event.getPlayer(), false); + } + } + + if (praxiPlayer.getRematchData() != null) { + final Player target = + Praxi.getInstance().getServer().getPlayer(praxiPlayer.getRematchData().getTarget()); + + if (target != null && target.isOnline()) { + PraxiPlayer.getByUuid(target.getUniqueId()).refreshHotbar(); + } + } + + TaskUtil.runAsync(praxiPlayer::save); + + if (praxiPlayer.isInMatch()) { + praxiPlayer.getMatch().handleDeath(event.getPlayer(), null, true); + } else if (praxiPlayer.isInQueue()) { + Queue queue = Queue.getByUuid(praxiPlayer.getQueuePlayer().getQueueUuid()); + + if (queue == null) { + return; + } + + queue.removePlayer(praxiPlayer.getQueuePlayer()); + } else if (praxiPlayer.isInEvent()) { + praxiPlayer.getEvent().handleLeave(event.getPlayer()); + } + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getItem() != null && event.getAction().name().contains("RIGHT")) { + final Player player = event.getPlayer(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.isInMatch()) { + if (event.getItem().hasItemMeta() && event.getItem().getItemMeta().hasDisplayName()) { + if (event.getItem().equals(Kit.DEFAULT_KIT)) { + event.setCancelled(true); + + final Kit kit = praxiPlayer.getMatch().getLadder().getDefaultKit(); + + player.getInventory().setArmorContents(kit.getArmor()); + player.getInventory().setContents(kit.getContents()); + player.updateInventory(); + player.sendMessage( + Style.YELLOW + "You have been given the" + Style.AQUA + " Default " + Style.YELLOW + + "kit."); + return; + } + } + + if (event.getItem().hasItemMeta() && event.getItem().getItemMeta().hasDisplayName()) { + final String displayName = ChatColor.stripColor(event.getItem().getItemMeta().getDisplayName()); + + if (displayName.startsWith("Kit: ")) { + final String kitName = displayName.replace("Kit: ", ""); + + for (NamedKit kit : praxiPlayer.getKits(praxiPlayer.getMatch().getLadder())) { + if (kit != null) { + if (ChatColor.stripColor(kit.getName()).equals(kitName)) { + event.setCancelled(true); + + player.getInventory().setArmorContents(kit.getArmor()); + player.getInventory().setContents(kit.getContents()); + player.updateInventory(); + player.sendMessage( + Style.YELLOW + "You have been given the " + Style.AQUA + kit.getName() + + Style.YELLOW + " kit."); + return; + } + } + } + } + } + + if (event.getItem().getType() == Material.ENDER_PEARL || + (event.getItem().getType() == Material.POTION && event.getItem().getDurability() >= 16_000)) { + if (praxiPlayer.isInMatch() && praxiPlayer.getMatch().isStarting()) { + event.setCancelled(true); + return; + } + } + + if (event.getItem().getType() == Material.ENDER_PEARL && event.getClickedBlock() == null) { + if (!praxiPlayer.isInMatch() || (praxiPlayer.isInMatch() && !praxiPlayer.getMatch().isFighting())) { + event.setCancelled(true); + return; + } + + if (praxiPlayer.getMatch().isStarting()) { + event.setCancelled(true); + return; + } + + if (!praxiPlayer.getEnderpearlCooldown().hasExpired()) { + final String time = + TimeUtil.millisToSeconds(praxiPlayer.getEnderpearlCooldown().getRemaining()); + final String context = "second" + (time.equalsIgnoreCase("1.0") ? "s" : ""); + + event.setCancelled(true); + player.sendMessage( + Style.YELLOW + "You are on pearl cooldown for " + Style.PINK + time + " " + context + + Style.YELLOW + "."); + } else { + praxiPlayer.setEnderpearlCooldown(new Cooldown(16_000)); + } + } + } else { + PlayerHotbar.HotbarItem hotbarItem = PlayerHotbar.fromItemStack(event.getItem()); + + if (hotbarItem == null) { + return; + } + + event.setCancelled(true); + + switch (hotbarItem) { + case QUEUE_JOIN_RANKED: { + if (praxiPlayer.isInLobby()) { + new QueueJoinMenu(true).openMenu(event.getPlayer()); + } + } + break; + case QUEUE_JOIN_UNRANKED: { + if (praxiPlayer.isInLobby()) { + new QueueJoinMenu(false).openMenu(event.getPlayer()); + } + } + break; + case QUEUE_LEAVE: { + if (praxiPlayer.isInQueue()) { + Queue queue = Queue.getByUuid(praxiPlayer.getQueuePlayer().getQueueUuid()); + + if (queue != null) { + queue.removePlayer(praxiPlayer.getQueuePlayer()); + } + } + } + break; + case SPECTATE_STOP: { + CommandHandler.executeCommand(event.getPlayer(), "stopspectate"); + } + break; + case PARTY_CREATE: { + CommandHandler.executeCommand(event.getPlayer(), "party create"); + } + break; + case PARTY_DISBAND: { + CommandHandler.executeCommand(event.getPlayer(), "party disband"); + } + break; + case PARTY_INFORMATION: { + CommandHandler.executeCommand(event.getPlayer(), "party info"); + } + break; + case PARTY_LEAVE: { + CommandHandler.executeCommand(event.getPlayer(), "party leave"); + } + break; + case PARTY_EVENTS: { + new PartyEventSelectEventMenu().openMenu(player); + } + break; + case OTHER_PARTIES: { + new OtherPartiesMenu().openMenu(event.getPlayer()); + } + break; + case KIT_EDITOR: { + if (praxiPlayer.isInLobby() || praxiPlayer.isInQueue()) { + new SelectLadderKitMenu().openMenu(event.getPlayer()); + } + } + break; + case SETTINGS: { + CommandHandler.executeCommand(event.getPlayer(), "settings"); + } + break; + case REMATCH_REQUEST: + case REMATCH_ACCEPT: { + CommandHandler.executeCommand(event.getPlayer(), "rematch"); + } + break; + case EVENT_JOIN: { + CommandHandler.executeCommand(event.getPlayer(), "event join"); + } + break; + case EVENT_LEAVE: { + CommandHandler.executeCommand(event.getPlayer(), "event leave"); + } + break; + } + } + } + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + event.setDeathMessage(null); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getEntity().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + final List entities = new ArrayList<>(); + + event.getDrops().forEach(itemStack -> { + entities.add(event.getEntity().getLocation().getWorld() + .dropItemNaturally(event.getEntity().getLocation(), itemStack)); + }); + event.getDrops().clear(); + + praxiPlayer.getMatch().getEntities().addAll(entities); + praxiPlayer.getMatch().handleDeath(event.getEntity(), event.getEntity().getKiller(), false); + } + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + event.setRespawnLocation(event.getPlayer().getLocation()); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + praxiPlayer.getMatch().handleRespawn(event.getPlayer()); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + final Match match = praxiPlayer.getMatch(); + + if (match.getLadder().isBuild() && praxiPlayer.getMatch().isFighting()) { + if (match.getLadder().isSpleef()) { + event.setCancelled(true); + return; + } + + final Arena arena = match.getArena(); + final int x = (int) event.getBlockPlaced().getLocation().getX(); + final int y = (int) event.getBlockPlaced().getLocation().getY(); + final int z = (int) event.getBlockPlaced().getLocation().getZ(); + + if (y > arena.getMaxBuildHeight()) { + event.getPlayer().sendMessage(Style.RED + "You have reached the maximum build height."); + event.setCancelled(true); + return; + } + + if (x >= arena.getX1() && x <= arena.getX2() && y >= arena.getY1() && y <= arena.getY2() && + z >= arena.getZ1() && z <= arena.getZ2()) { + match.getPlacedBlocks().add(event.getBlock().getLocation()); + } else { + event.setCancelled(true); + } + } else { + event.setCancelled(true); + } + } else { + if (event.getPlayer().getGameMode() != GameMode.CREATIVE || !event.getPlayer().isOp()) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBucketEmpty(PlayerBucketEmptyEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + final Match match = praxiPlayer.getMatch(); + + if (match.getLadder().isBuild() && praxiPlayer.getMatch().isFighting()) { + final Arena arena = match.getArena(); + final Block block = event.getBlockClicked().getRelative(event.getBlockFace()); + final int x = (int) block.getLocation().getX(); + final int y = (int) block.getLocation().getY(); + final int z = (int) block.getLocation().getZ(); + + if (y > arena.getMaxBuildHeight()) { + event.getPlayer().sendMessage(Style.RED + "You have reached the maximum build height."); + event.setCancelled(true); + return; + } + + if (x >= arena.getX1() && x <= arena.getX2() && y >= arena.getY1() && y <= arena.getY2() && + z >= arena.getZ1() && z <= arena.getZ2()) { + match.getPlacedBlocks().add(block.getLocation()); + } else { + event.setCancelled(true); + } + } else { + event.setCancelled(true); + } + } else { + if (event.getPlayer().getGameMode() != GameMode.CREATIVE || !event.getPlayer().isOp()) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + final Match match = praxiPlayer.getMatch(); + + if (match.getLadder().isBuild() && praxiPlayer.getMatch().isFighting()) { + if (match.getLadder().isSpleef()) { + if (event.getBlock().getType() == Material.SNOW_BLOCK || + event.getBlock().getType() == Material.SNOW) { + match.getChangedBlocks().add(event.getBlock().getState()); + + event.getBlock().setType(Material.AIR); + event.getPlayer().getInventory().addItem(new ItemStack(Material.SNOW_BALL, 4)); + event.getPlayer().updateInventory(); + } else { + event.setCancelled(true); + } + } else if (!match.getPlacedBlocks().remove(event.getBlock().getLocation())) { + event.setCancelled(true); + } + } else { + event.setCancelled(true); + } + } else { + if (event.getPlayer().getGameMode() != GameMode.CREATIVE || !event.getPlayer().isOp()) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerPickupItem(PlayerPickupItemEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInMatch()) { + if (!praxiPlayer.getMatch().getMatchPlayer(event.getPlayer()).isAlive()) { + event.setCancelled(true); + return; + } + + Iterator entityIterator = praxiPlayer.getMatch().getEntities().iterator(); + + while (entityIterator.hasNext()) { + Entity entity = entityIterator.next(); + + if (entity instanceof Item && entity.equals(event.getItem())) { + entityIterator.remove(); + return; + } + } + + event.setCancelled(true); + } else if (praxiPlayer.isSpectating()) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerDropItem(PlayerDropItemEvent event) { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (event.getItemDrop().getItemStack().getType() == Material.BOOK || + event.getItemDrop().getItemStack().getType() == Material.ENCHANTED_BOOK) { + event.getItemDrop().remove(); + return; + } + + if (praxiPlayer.isInMatch()) { + if (praxiPlayer.getMatch() != null) { + if (event.getItemDrop().getItemStack().getType() == Material.GLASS_BOTTLE) { + event.getItemDrop().remove(); + return; + } + + praxiPlayer.getMatch().getEntities().add(event.getItemDrop()); + } + } else { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getWhoClicked() instanceof Player) { + final Player player = (Player) event.getWhoClicked(); + + if (event.getClickedInventory() != null && event.getClickedInventory() instanceof CraftingInventory) { + if (player.getGameMode() != GameMode.CREATIVE) { + event.setCancelled(true); + return; + } + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (!praxiPlayer.isInMatch() && player.getGameMode() == GameMode.SURVIVAL) { + final Inventory clicked = event.getClickedInventory(); + + if (praxiPlayer.getKitEditor().isActive()) { + if (clicked == null) { + event.setCancelled(true); + event.setCursor(null); + player.updateInventory(); + } else if (clicked.equals(player.getOpenInventory().getTopInventory())) { + if (event.getCursor().getType() != Material.AIR && + event.getCurrentItem().getType() == Material.AIR || + event.getCursor().getType() != Material.AIR && + event.getCurrentItem().getType() != Material.AIR) { + event.setCancelled(true); + event.setCursor(null); + player.updateInventory(); + } + } + } else { + if (clicked != null && clicked.equals(player.getInventory())) { + event.setCancelled(true); + } + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerItemDamage(PlayerItemDamageEvent event) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(event.getPlayer().getUniqueId()); + + if (praxiPlayer.isInLobby()) { + event.setCancelled(true); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/ProjectileListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/ProjectileListener.java new file mode 100644 index 0000000..b37e388 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/ProjectileListener.java @@ -0,0 +1,58 @@ +package me.joeleoli.praxi.listener; + +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +public class ProjectileListener implements Listener { + + @EventHandler(ignoreCancelled = true) + public void onProjectileLaunch(ProjectileLaunchEvent event) { + if (event.getEntity() instanceof ThrownPotion) { + if (event.getEntity().getShooter() instanceof Player) { + final Player shooter = (Player) event.getEntity().getShooter(); + final PraxiPlayer shooterData = PraxiPlayer.getByUuid(shooter.getUniqueId()); + + if (shooterData.isInMatch() && shooterData.getMatch().isFighting()) { + shooterData.getMatch().getMatchPlayer(shooter).incrementPotionsThrown(); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onProjectileHit(ProjectileHitEvent event) { + if (event.getEntity() instanceof Arrow) { + if (event.getEntity().getShooter() instanceof Player) { + final Player shooter = (Player) event.getEntity().getShooter(); + final PraxiPlayer shooterData = PraxiPlayer.getByUuid(shooter.getUniqueId()); + + if (shooterData.isInMatch()) { + shooterData.getMatch().getEntities().add(event.getEntity()); + shooterData.getMatch().getMatchPlayer(shooter).handleHit(); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPotionSplash(PotionSplashEvent event) { + if (event.getPotion().getShooter() instanceof Player) { + final Player shooter = (Player) event.getPotion().getShooter(); + final PraxiPlayer shooterData = PraxiPlayer.getByUuid(shooter.getUniqueId()); + + if (shooterData.isInMatch() && shooterData.getMatch().isFighting()) { + if (event.getIntensity(shooter) <= 0.5D) { + shooterData.getMatch().getMatchPlayer(shooter).incrementPotionsMissed(); + } + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/ServerListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/ServerListener.java new file mode 100644 index 0000000..5143abd --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/ServerListener.java @@ -0,0 +1,31 @@ +package me.joeleoli.praxi.listener; + +import me.joeleoli.nucleus.Nucleus; +import me.joeleoli.nucleus.event.PreShutdownEvent; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ServerListener implements Listener { + + @EventHandler + public void onPreShutdown(PreShutdownEvent event) { + Nucleus.getInstance().getServer().getScheduler().runTaskAsynchronously(Nucleus.getInstance(), () -> { + for (Player player : Nucleus.getInstance().getServer().getOnlinePlayers()) { + PraxiPlayer.getByUuid(player.getUniqueId()).save(); + } + }); + + for (Match match : Match.getMatches()) { + match.getPlacedBlocks().forEach(location -> location.getBlock().setType(Material.AIR)); + match.getChangedBlocks() + .forEach((blockState) -> blockState.getLocation().getBlock().setType(blockState.getType())); + match.getEntities().forEach(Entity::remove); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/listener/WorldListener.java b/plugin/src/main/java/me/joeleoli/praxi/listener/WorldListener.java new file mode 100644 index 0000000..63bfc73 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/listener/WorldListener.java @@ -0,0 +1,66 @@ +package me.joeleoli.praxi.listener; + +import org.bukkit.Difficulty; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockSpreadEvent; +import org.bukkit.event.block.LeavesDecayEvent; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.weather.WeatherChangeEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldLoadEvent; + +public class WorldListener implements Listener { + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + event.getWorld().getEntities().clear(); + event.getWorld().setDifficulty(Difficulty.HARD); + } + + @EventHandler + public void onWeatherChange(WeatherChangeEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onCreatureSpawn(CreatureSpawnEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockIgnite(BlockIgniteEvent event) { + if (event.getCause() == BlockIgniteEvent.IgniteCause.LIGHTNING) { + event.setCancelled(true); + } + } + + @EventHandler + public void onLeavesDecay(LeavesDecayEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onHangingBreak(HangingBreakEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockBurn(BlockBurnEvent event) { + event.setCancelled(true); + } + + @EventHandler + public void onBlockSpread(BlockSpreadEvent event) { + event.setCancelled(true); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/Match.java b/plugin/src/main/java/me/joeleoli/praxi/match/Match.java new file mode 100644 index 0000000..4b8510e --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/Match.java @@ -0,0 +1,722 @@ +package me.joeleoli.praxi.match; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.fairfight.FairFight; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.nametag.NameTagHandler; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.nucleus.util.TimeUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.player.RematchData; +import me.joeleoli.ragespigot.RageSpigot; +import me.joeleoli.ragespigot.knockback.KnockbackProfile; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.server.v1_8_R3.EntityLightning; +import net.minecraft.server.v1_8_R3.PacketPlayOutSpawnEntityWeather; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +@Getter +public abstract class Match { + + protected static final BaseComponent[] HOVER_TEXT = + new ChatComponentBuilder(Style.GRAY + "Click to view this player's inventory.").create(); + @Getter + protected static List matches = new ArrayList<>(); + private UUID matchId = UUID.randomUUID(); + @Setter + private MatchState state = MatchState.STARTING; + private UUID queueId; + @Setter + private Ladder ladder; + private Arena arena; + private boolean ranked; + private List snapshots = new ArrayList<>(); + private List spectators = new ArrayList<>(); + private List entities = new ArrayList<>(); + private List placedBlocks = new ArrayList<>(); + private List changedBlocks = new ArrayList<>(); + @Setter + private long startTimestamp; + + public Match(Ladder ladder, Arena arena, boolean ranked) { + this(null, ladder, arena, ranked); + } + + public Match(UUID queueId, Ladder ladder, Arena arena, boolean ranked) { + this.queueId = queueId; + this.ladder = ladder; + this.arena = arena; + this.ranked = ranked; + + matches.add(this); + } + + public boolean isMatchMakingMatch() { + return this.queueId != null; + } + + public boolean isStarting() { + return this.state == MatchState.STARTING; + } + + public boolean isFighting() { + return this.state == MatchState.FIGHTING; + } + + public boolean isEnding() { + return this.state == MatchState.ENDING; + } + + public void setupPlayers() { + if (this.isSoloMatch()) { + final MatchPlayer matchPlayerA = this.getMatchPlayerA(); + final MatchPlayer matchPlayerB = this.getMatchPlayerB(); + + matchPlayerA.setAlive(true); + matchPlayerB.setAlive(true); + + final Player playerA = matchPlayerA.toPlayer(); + final Player playerB = matchPlayerB.toPlayer(); + + playerA.showPlayer(playerB); + playerB.showPlayer(playerA); + + if (this.arena.getSpawn1().getBlock().getType() == Material.AIR) { + playerA.teleport(this.arena.getSpawn1()); + } else { + playerA.teleport(this.arena.getSpawn1().add(0, 2, 0)); + } + + if (this.arena.getSpawn2().getBlock().getType() == Material.AIR) { + playerB.teleport(this.arena.getSpawn2()); + } else { + playerB.teleport(this.arena.getSpawn2().add(0, 2, 0)); + } + + NameTagHandler.addToTeam(playerA, playerB, ChatColor.RED, this.ladder.isBuild()); + NameTagHandler.addToTeam(playerB, playerA, ChatColor.RED, this.ladder.isBuild()); + + PlayerUtil.reset(playerA); + PlayerUtil.reset(playerB); + + playerA.setMaximumNoDamageTicks(this.ladder.getHitDelay()); + playerB.setMaximumNoDamageTicks(this.ladder.getHitDelay()); + + if (this.ladder.isSumo()) { + FairFight.getInstance().getPlayerDataManager().getPlayerData(playerA).setAllowTeleport(true); + FairFight.getInstance().getPlayerDataManager().getPlayerData(playerB).setAllowTeleport(true); + + PlayerUtil.denyMovement(playerA); + PlayerUtil.denyMovement(playerB); + } else { + for (ItemStack itemStack : PraxiPlayer.getByUuid(playerA.getUniqueId()).getKitItems(this.ladder)) { + playerA.getInventory().addItem(itemStack); + } + + for (ItemStack itemStack : PraxiPlayer.getByUuid(playerB.getUniqueId()).getKitItems(this.ladder)) { + playerB.getInventory().addItem(itemStack); + } + } + + if (this.ladder.getKbProfile() != null) { + final KnockbackProfile profile = + RageSpigot.INSTANCE.getConfig().getKbProfileByName(this.ladder.getKbProfile()); + + if (profile != null) { + playerA.setKnockbackProfile(profile); + playerB.setKnockbackProfile(profile); + } + } + } else if (this.isTeamMatch()) { + final MatchTeam teamA = this.getTeamA(); + final MatchTeam teamB = this.getTeamB(); + + for (MatchPlayer matchPlayer : teamA.getTeamPlayers()) { + if (!matchPlayer.isDisconnected()) { + matchPlayer.setAlive(true); + } + + final Player player = matchPlayer.toPlayer(); + + if (player == null || !player.isOnline()) { + continue; + } + + player.teleport(this.arena.getSpawn1()); + + for (Player member : teamA.getPlayers()) { + NameTagHandler.addToTeam(player, member, ChatColor.GREEN, this.ladder.isBuild()); + } + + for (Player enemy : teamB.getPlayers()) { + NameTagHandler.addToTeam(player, enemy, ChatColor.RED, this.ladder.isBuild()); + } + } + + for (MatchPlayer matchPlayer : teamB.getTeamPlayers()) { + if (!matchPlayer.isDisconnected()) { + matchPlayer.setAlive(true); + } + + final Player player = matchPlayer.toPlayer(); + + if (player == null || !player.isOnline()) { + continue; + } + + player.teleport(this.arena.getSpawn2()); + + for (Player member : teamB.getPlayers()) { + NameTagHandler.addToTeam(player, member, ChatColor.GREEN, this.ladder.isBuild()); + } + + for (Player enemy : teamA.getPlayers()) { + NameTagHandler.addToTeam(player, enemy, ChatColor.RED, this.ladder.isBuild()); + } + } + + final List players = this.getPlayers(); + + for (Player first : players) { + PlayerUtil.reset(first); + + first.setMaximumNoDamageTicks(this.ladder.getHitDelay()); + + if (this.ladder.isSumo()) { + FairFight.getInstance().getPlayerDataManager().getPlayerData(first).setAllowTeleport(true); + + PlayerUtil.denyMovement(first); + } else { + for (ItemStack itemStack : PraxiPlayer.getByUuid(first.getUniqueId()).getKitItems(this.ladder)) { + first.getInventory().addItem(itemStack); + } + } + + if (this.ladder.getKbProfile() != null) { + final KnockbackProfile profile = + RageSpigot.INSTANCE.getConfig().getKbProfileByName(this.ladder.getKbProfile()); + + if (profile != null) { + first.setKnockbackProfile(profile); + } + } + + for (Player second : players) { + if (first.getUniqueId().equals(second.getUniqueId())) { + continue; + } + + first.showPlayer(second); + second.showPlayer(first); + } + } + } + } + + public void handleStart() { + this.setupPlayers(); + + this.state = MatchState.STARTING; + this.startTimestamp = -1; + this.arena.setActive(true); + + this.onStart(); + + this.getPlayers().forEach(player -> { + player.sendMessage(Style.YELLOW + "You are playing on arena " + Style.PINK + this.arena.getName() + Style.YELLOW + "."); + }); + + TaskUtil.runTimer(new MatchStartTask(this), 20L, 20L); + } + + private void handleEnd() { + this.state = MatchState.ENDING; + + this.onEnd(); + + if (this.isSoloMatch()) { + final Player playerA = this.getPlayerA(); + final Player playerB = this.getPlayerB(); + + playerA.hidePlayer(playerB); + playerB.hidePlayer(playerA); + + for (MatchPlayer matchPlayer : new MatchPlayer[]{ this.getMatchPlayerA(), this.getMatchPlayerB() }) { + if (matchPlayer.isAlive()) { + final Player player = matchPlayer.toPlayer(); + + if (player != null) { + player.setFireTicks(0); + player.updateInventory(); + + if (matchPlayer.isAlive()) { + MatchSnapshot snapshot = new MatchSnapshot(matchPlayer); + + snapshot.setSwitchTo(this.getOpponentMatchPlayer(player)); + + this.snapshots.add(snapshot); + } + } + } + } + } else if (this.isTeamMatch()) { + for (MatchPlayer firstMatchPlayer : this.getMatchPlayers()) { + if (firstMatchPlayer.isDisconnected()) { + continue; + } + + final Player player = firstMatchPlayer.toPlayer(); + + if (player != null) { + for (MatchPlayer secondMatchPlayer : this.getMatchPlayers()) { + if (secondMatchPlayer.isDisconnected()) { + continue; + } + + if (secondMatchPlayer.getUuid().equals(player.getUniqueId())) { + continue; + } + + final Player secondPlayer = secondMatchPlayer.toPlayer(); + + if (secondPlayer == null) { + continue; + } + + player.hidePlayer(secondPlayer); + } + + player.setFireTicks(0); + player.updateInventory(); + + if (firstMatchPlayer.isAlive()) { + this.snapshots.add(new MatchSnapshot(firstMatchPlayer)); + } + } + } + } + + this.getSpectators().forEach(this::removeSpectator); + this.entities.forEach(Entity::remove); + this.snapshots.forEach(matchInventory -> { + matchInventory.setCreated(System.currentTimeMillis()); + MatchSnapshot.getCache().put(matchInventory.getMatchPlayer().getUuid(), matchInventory); + }); + + new MatchResetTask(this).runTask(Praxi.getInstance()); + + TaskUtil.runLater(() -> { + if (this.isSoloMatch()) { + final UUID rematchKey = UUID.randomUUID(); + final Player playerA = this.getPlayerA(); + final Player playerB = this.getPlayerB(); + + if (playerA != null && playerB != null) { + NameTagHandler.removeFromTeams(playerA, playerB); + NameTagHandler.removeFromTeams(playerB, playerA); + NameTagHandler.removeHealthDisplay(playerA); + NameTagHandler.removeHealthDisplay(playerB); + } + + for (MatchPlayer matchPlayer : new MatchPlayer[]{ this.getMatchPlayerA(), this.getMatchPlayerB() }) { + final Player player = matchPlayer.toPlayer(); + final Player opponent = this.getOpponentPlayer(player); + + if (player != null) { + player.setKnockbackProfile(null); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (opponent != null) { + praxiPlayer.setRematchData( + new RematchData(rematchKey, player.getUniqueId(), opponent.getUniqueId(), + this.getLadder(), this.getArena() + )); + } + + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.setMatch(null); + praxiPlayer.loadHotbar(); + + PlayerUtil.spawn(player); + } + } + } else if (this.isTeamMatch()) { + this.getPlayers().forEach(player -> { + NameTagHandler.removeHealthDisplay(player); + this.getPlayers().forEach(otherPlayer -> NameTagHandler.removeFromTeams(player, otherPlayer)); + }); + + for (MatchPlayer matchPlayer : this.getMatchPlayers()) { + if (matchPlayer.isDisconnected()) { + continue; + } + + final Player player = matchPlayer.toPlayer(); + + if (player != null) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.setMatch(null); + praxiPlayer.loadHotbar(); + + player.setKnockbackProfile(null); + + PlayerUtil.spawn(player); + } + } + } + }, 20L * 3); + + matches.remove(this); + } + + public void handleRespawn(Player player) { + player.spigot().respawn(); + player.setVelocity(new Vector()); + + this.onRespawn(player); + } + + public void handleDeath(Player deadPlayer, Player killerPlayer, boolean disconnected) { + final MatchPlayer matchPlayer = this.getMatchPlayer(deadPlayer); + + if (!matchPlayer.isAlive()) { + return; + } + + matchPlayer.setAlive(false); + matchPlayer.setDisconnected(disconnected); + + final List involvedPlayers = new ArrayList<>(); + + if (this.isSoloMatch()) { + involvedPlayers.add(this.getPlayerA()); + involvedPlayers.add(this.getPlayerB()); + } else { + involvedPlayers.addAll(this.getPlayers()); + } + + involvedPlayers.addAll(this.getSpectators()); + + EntityLightning is = + new EntityLightning(((CraftWorld) deadPlayer.getWorld()).getHandle(), deadPlayer.getLocation().getX(), + deadPlayer.getLocation().getY(), deadPlayer.getLocation().getZ() + ); + PacketPlayOutSpawnEntityWeather lightningPacket = new PacketPlayOutSpawnEntityWeather(is); + + involvedPlayers.forEach(other -> { + if (other != null) { + other.playSound(deadPlayer.getLocation(), Sound.AMBIENCE_THUNDER, 1.0F, 1.0F); + ((CraftPlayer) other).getHandle().playerConnection.sendPacket(lightningPacket); + } + }); + + for (Player involved : involvedPlayers) { + String deadName = Style.RED + deadPlayer.getName(); + + if (this.isSoloMatch()) { + // Todo: fix NPE here, idk where but it says the line right below... + // DEBUG FOR NOW: + + if (deadPlayer == null) { + System.out.println("DEBUG: DEAD PLAYER NULL"); + continue; + } + + if (involved == null) { + System.out.println("DEBUG: INVOLVED PLAYER NULL"); + continue; + } + + if (deadPlayer.getUniqueId().equals(involved.getUniqueId())) { + deadName = Style.GREEN + deadPlayer.getName(); + } + } else { + final MatchTeam matchTeam = this.getTeam(involved); + + if (matchTeam != null && matchTeam.containsPlayer(deadPlayer)) { + deadName = Style.GREEN + deadPlayer.getName(); + } + } + + if (matchPlayer.isDisconnected()) { + involved.sendMessage(deadName + Style.YELLOW + " has disconnected."); + continue; + } + + String killerName = null; + + if (killerPlayer != null) { + killerName = Style.RED + killerPlayer.getName(); + + if (this.isSoloMatch()) { + if (killerPlayer.getUniqueId().equals(involved.getUniqueId())) { + killerName = Style.GREEN + killerPlayer.getName(); + } + } else { + final MatchTeam matchTeam = this.getTeam(involved); + + if (matchTeam != null && matchTeam.containsPlayer(killerPlayer)) { + killerName = Style.GREEN + killerPlayer.getName(); + } + } + } + + if (killerName == null) { + involved.sendMessage(deadName + Style.YELLOW + " has died."); + } else { + involved.sendMessage(deadName + Style.YELLOW + " was killed by " + killerName + Style.YELLOW + "."); + } + } + + this.onDeath(deadPlayer, killerPlayer); + + if (this.canEnd()) { + this.handleEnd(); + } + } + + public String getDuration() { + if (this.isStarting()) { + return "00:00"; + } else if (this.isEnding()) { + return "Ending"; + } else { + return TimeUtil.millisToTimer(this.getElapsedDuration()); + } + } + + public long getElapsedDuration() { + return System.currentTimeMillis() - this.startTimestamp; + } + + public void broadcastMessage(String message) { + this.getPlayers().forEach(player -> player.sendMessage(message)); + this.getSpectators().forEach(player -> player.sendMessage(message)); + } + + public void broadcastSound(Sound sound) { + this.getPlayers().forEach(player -> player.playSound(player.getLocation(), sound, 1.0F, 1.0F)); + this.getSpectators().forEach(player -> player.playSound(player.getLocation(), sound, 1.0F, 1.0F)); + } + + public List getInvolvedPlayers() { + List toReturn = new ArrayList<>(); + + toReturn.addAll(this.spectators); + + if (this.isSoloMatch()) { + toReturn.add(this.getMatchPlayerA().getUuid()); + toReturn.add(this.getMatchPlayerB().getUuid()); + } else if (this.isTeamMatch()) { + this.getMatchPlayers().forEach(matchPlayer -> toReturn.add(matchPlayer.getUuid())); + } + + return toReturn; + } + + protected List getSpectators() { + return PlayerUtil.convertUUIDListToPlayerList(this.spectators); + } + + public void addSpectator(Player player, Player target) { + this.spectators.add(player.getUniqueId()); + + if (this.isSoloMatch()) { + final Player playerA = this.getPlayerA(); + final Player playerB = this.getPlayerB(); + + if (playerA != null) { + player.showPlayer(playerA); + + NameTagHandler.addToTeam(player, playerA, ChatColor.AQUA, this.ladder.isBuild()); + } + + if (playerB != null) { + player.showPlayer(playerB); + + NameTagHandler.addToTeam(player, playerB, ChatColor.LIGHT_PURPLE, this.ladder.isBuild()); + } + } else if (this.isTeamMatch()) { + this.getTeamA().getPlayers().forEach(teamPlayer -> { + player.showPlayer(teamPlayer); + teamPlayer.hidePlayer(player); + NameTagHandler.addToTeam(player, teamPlayer, ChatColor.AQUA, this.ladder.isBuild()); + }); + + this.getTeamB().getPlayers().forEach(teamPlayer -> { + player.showPlayer(teamPlayer); + teamPlayer.hidePlayer(player); + NameTagHandler.addToTeam(player, teamPlayer, ChatColor.LIGHT_PURPLE, this.ladder.isBuild()); + }); + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setMatch(this); + praxiPlayer.setState(PlayerState.SPECTATE_MATCH); + praxiPlayer.loadHotbar(); + + player.setAllowFlight(true); + player.setFlying(true); + player.updateInventory(); + player.teleport(target.getLocation().clone().add(0, 2, 0)); + player.sendMessage(Style.YELLOW + "You are spectating " + Style.PINK + target.getName() + Style.YELLOW + "."); + + if (this.isSoloMatch()) { + for (Player matchPlayer : new Player[]{ this.getPlayerA(), this.getPlayerB() }) { + if (!player.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.PINK + player.getName() + Style.YELLOW + " is now spectating."); + + } else if (matchPlayer.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.GRAY + "[Silent] " + Style.PINK + player.getName() + Style.YELLOW + + " is now spectating."); + } + } + } else if (this.isTeamMatch()) { + for (Player matchPlayer : this.getPlayers()) { + if (!player.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.PINK + player.getName() + Style.YELLOW + " is now spectating."); + + } else if (matchPlayer.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.GRAY + "[Silent] " + Style.PINK + player.getName() + Style.YELLOW + + " is now spectating."); + } + } + } + } + + public void removeSpectator(Player player) { + this.spectators.remove(player.getUniqueId()); + + if (this.isSoloMatch()) { + player.hidePlayer(this.getPlayerA()); + player.hidePlayer(this.getPlayerB()); + + NameTagHandler.removeFromTeams(player, this.getPlayerA()); + NameTagHandler.removeFromTeams(player, this.getPlayerB()); + NameTagHandler.removeHealthDisplay(player); + } else if (this.isTeamMatch()) { + this.getPlayers().forEach(other -> { + player.hidePlayer(other); + other.hidePlayer(player); + NameTagHandler.removeFromTeams(player, other); + }); + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (this.state != MatchState.ENDING) { + final String toSend = Style.PINK + player.getName() + Style.YELLOW + " is no longer spectating your match."; + + if (this.isSoloMatch()) { + for (Player matchPlayer : new Player[]{ this.getPlayerA(), this.getPlayerB() }) { + if (!player.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(toSend); + } else if (matchPlayer.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.GRAY + "[Silent] " + toSend); + } + } + } else if (this.isTeamMatch()) { + for (Player matchPlayer : this.getPlayers()) { + if (!player.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(toSend); + } else if (matchPlayer.hasPermission("praxi.spectate.hidden")) { + matchPlayer.sendMessage(Style.GRAY + "[Silent] " + toSend); + } + } + } + } + + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.setMatch(null); + praxiPlayer.loadHotbar(); + + PlayerUtil.spawn(player); + } + + public abstract boolean isDuel(); + + public abstract boolean isSoloMatch(); + + public abstract boolean isTeamMatch(); + + public abstract void onStart(); + + public abstract void onEnd(); + + public abstract void onDeath(Player player, Player killer); + + public abstract void onRespawn(Player player); + + public abstract boolean canEnd(); + + public abstract Player getWinningPlayer(); + + public abstract MatchTeam getWinningTeam(); + + public abstract MatchPlayer getMatchPlayerA(); + + public abstract MatchPlayer getMatchPlayerB(); + + public abstract List getMatchPlayers(); + + public abstract Player getPlayerA(); + + public abstract Player getPlayerB(); + + public abstract List getPlayers(); + + public abstract MatchTeam getTeamA(); + + public abstract MatchTeam getTeamB(); + + public abstract MatchTeam getTeam(MatchPlayer matchPlayer); + + public abstract MatchTeam getTeam(Player player); + + public abstract MatchPlayer getMatchPlayer(Player player); + + public abstract int getOpponentsLeft(Player player); + + public abstract MatchTeam getOpponentTeam(MatchTeam matchTeam); + + public abstract MatchTeam getOpponentTeam(Player player); + + public abstract MatchPlayer getOpponentMatchPlayer(Player player); + + public abstract Player getOpponentPlayer(Player player); + + public abstract int getTotalRoundWins(); + + public abstract int getRoundWins(MatchPlayer matchPlayer); + + public abstract int getRoundWins(MatchTeam matchTeam); + + public abstract int getRoundsNeeded(MatchPlayer matchPlayer); + + public abstract int getRoundsNeeded(MatchTeam matchTeam); + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchPlayer.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchPlayer.java new file mode 100644 index 0000000..bcef4fa --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchPlayer.java @@ -0,0 +1,51 @@ +package me.joeleoli.praxi.match; + +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.team.TeamPlayer; +import org.bukkit.entity.Player; + +@Getter +@Setter +public class MatchPlayer extends TeamPlayer { + + private boolean alive = true; + private boolean disconnected; + private int elo, potionsThrown, potionsMissed, hits, combo, longestCombo; + + public MatchPlayer(Player player) { + super(player.getUniqueId(), player.getName()); + } + + public double getPotionAccuracy() { + if (this.potionsMissed == 0) { + return 100.0; + } else if (this.potionsThrown == this.potionsMissed) { + return 50.0; + } + + return Math.round(100.0D - (((double) this.potionsMissed / (double) this.potionsThrown) * 100.0D)); + } + + public void incrementPotionsThrown() { + this.potionsThrown++; + } + + public void incrementPotionsMissed() { + this.potionsMissed++; + } + + public void handleHit() { + this.hits++; + this.combo++; + + if (this.combo > this.longestCombo) { + this.longestCombo = this.combo; + } + } + + public void resetCombo() { + this.combo = 0; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchResetTask.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchResetTask.java new file mode 100644 index 0000000..2f7fc47 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchResetTask.java @@ -0,0 +1,82 @@ +package me.joeleoli.praxi.match; + +import com.boydti.fawe.util.EditSessionBuilder; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import lombok.AllArgsConstructor; +import org.bukkit.Location; +import org.bukkit.block.BlockState; +import org.bukkit.scheduler.BukkitRunnable; + +@AllArgsConstructor +public class MatchResetTask extends BukkitRunnable { + + private Match match; + + @Override + public void run() { + if (this.match.getLadder().isBuild() && this.match.getPlacedBlocks().size() > 0) { + TaskManager.IMP.async(() -> { + EditSession editSession = new EditSessionBuilder(this.match.getArena().getSpawn1().getWorld().getName()) + .fastmode(true) + .allowedRegionsEverywhere() + .autoQueue(false) + .limitUnlimited() + .build(); + + for (Location location : this.match.getPlacedBlocks()) { + try { + editSession.setBlock( + new Vector((double) location.getBlockX(), (double) location.getBlockY(), + location.getZ() + ), new BaseBlock(0)); + } catch (Exception ex) { + } + } + + editSession.flushQueue(); + + TaskManager.IMP.task(() -> { + this.match.getPlacedBlocks().clear(); + this.match.getArena().setActive(false); + this.cancel(); + }); + }); + } else if (this.match.getLadder().isBuild() && this.match.getChangedBlocks().size() > 0) { + TaskManager.IMP.async(() -> { + EditSession editSession = new EditSessionBuilder(this.match.getArena().getSpawn1().getWorld().getName()) + .fastmode(true) + .allowedRegionsEverywhere() + .autoQueue(false) + .limitUnlimited() + .build(); + + for (BlockState blockState : this.match.getChangedBlocks()) { + try { + editSession.setBlock( + new Vector(blockState.getLocation().getBlockX(), blockState.getLocation().getBlockY(), + blockState.getLocation().getZ() + ), new BaseBlock(blockState.getTypeId(), blockState.getRawData())); + } catch (Exception ex) { + } + } + + editSession.flushQueue(); + + TaskManager.IMP.task(() -> { + if (this.match.getLadder().isBuild()) { + this.match.getChangedBlocks().clear(); + this.match.getArena().setActive(false); + } + + this.cancel(); + }); + }); + } else { + this.cancel(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchSnapshot.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchSnapshot.java new file mode 100644 index 0000000..b860979 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchSnapshot.java @@ -0,0 +1,77 @@ +package me.joeleoli.praxi.match; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import lombok.Data; +import lombok.Getter; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +@Data +public class MatchSnapshot { + + @Getter + private static Map cache = new HashMap<>(); + + private MatchPlayer matchPlayer; + private MatchPlayer switchTo; + private int health; + private int hunger; + private ItemStack[] armor; + private ItemStack[] contents; + private Collection effects; + private long created = System.currentTimeMillis(); + + public MatchSnapshot(MatchPlayer matchPlayer) { + this(matchPlayer, null); + } + + public MatchSnapshot(MatchPlayer matchPlayer, MatchPlayer switchTo) { + this.matchPlayer = matchPlayer; + + final Player player = this.matchPlayer.toPlayer(); + + this.health = player.getHealth() == 0 ? 0 : (int) Math.round(player.getHealth() / 2); + this.hunger = player.getFoodLevel(); + this.armor = player.getInventory().getArmorContents(); + this.contents = player.getInventory().getContents(); + this.effects = player.getActivePotionEffects(); + this.switchTo = switchTo; + } + + public static MatchSnapshot getByUuid(UUID uuid) { + return cache.get(uuid); + } + + public static MatchSnapshot getByName(String name) { + for (MatchSnapshot details : cache.values()) { + if (details.getMatchPlayer().getName().equalsIgnoreCase(name)) { + return details; + } + } + + return null; + } + + public int getRemainingPotions() { + int amount = 0; + + for (ItemStack itemStack : this.contents) { + if (itemStack != null && itemStack.getType() == Material.POTION && itemStack.getDurability() == 16421) { + amount++; + } + } + + return amount; + } + + public boolean shouldDisplayRemainingPotions() { + return this.getRemainingPotions() > 0 || this.matchPlayer.getPotionsThrown() > 0 || + this.matchPlayer.getPotionsMissed() > 0; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchStartTask.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchStartTask.java new file mode 100644 index 0000000..6a5b311 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchStartTask.java @@ -0,0 +1,90 @@ +package me.joeleoli.praxi.match; + +import me.joeleoli.fairfight.FairFight; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class MatchStartTask extends BukkitRunnable { + + private Match match; + private int ticks; + + public MatchStartTask(Match match) { + this.match = match; + } + + @Override + public void run() { + int seconds = 5 - this.ticks; + + if (this.match.isEnding()) { + this.cancel(); + return; + } + + if (this.match.getLadder().isSumo()) { + if (seconds == 2) { + if (this.match.isSoloMatch()) { + final Player playerA = this.match.getPlayerA(); + final Player playerB = this.match.getPlayerB(); + + if (playerA != null) { + FairFight.getInstance().getPlayerDataManager().getPlayerData(playerA).setAllowTeleport(false); + + PlayerUtil.allowMovement(playerA); + } + + if (playerB != null) { + FairFight.getInstance().getPlayerDataManager().getPlayerData(playerB).setAllowTeleport(false); + + PlayerUtil.allowMovement(playerB); + } + } else if (this.match.isTeamMatch()) { + this.match.getMatchPlayers().forEach(matchPlayer -> { + if (!matchPlayer.isDisconnected()) { + final Player player = matchPlayer.toPlayer(); + + if (player != null) { + FairFight.getInstance().getPlayerDataManager().getPlayerData(player) + .setAllowTeleport(false); + + PlayerUtil.allowMovement(player); + } + } + }); + } + + this.match.setState(MatchState.FIGHTING); + this.match.setStartTimestamp(System.currentTimeMillis()); + this.match.broadcastMessage(Style.YELLOW + "The round has started!"); + this.match.broadcastSound(Sound.NOTE_BASS); + this.cancel(); + return; + } + + this.match.broadcastMessage( + Style.YELLOW + "The round will start in " + Style.PINK + (seconds - 2) + " second" + + (seconds - 2 == 1 ? "" : "s") + Style.YELLOW + "..."); + this.match.broadcastSound(Sound.NOTE_PLING); + } else { + if (seconds == 0) { + this.match.setState(MatchState.FIGHTING); + this.match.setStartTimestamp(System.currentTimeMillis()); + this.match.broadcastMessage(Style.YELLOW + "The match has started!"); + this.match.broadcastSound(Sound.NOTE_BASS); + this.cancel(); + return; + } + + this.match.broadcastMessage(Style.YELLOW + "The match will start in " + Style.PINK + seconds + " second" + + (seconds == 1 ? "" : "s") + Style.YELLOW + "..."); + this.match.broadcastSound(Sound.NOTE_PLING); + } + + this.ticks++; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchState.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchState.java new file mode 100644 index 0000000..f599817 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchState.java @@ -0,0 +1,9 @@ +package me.joeleoli.praxi.match; + +public enum MatchState { + + STARTING, + FIGHTING, + ENDING + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/MatchTeam.java b/plugin/src/main/java/me/joeleoli/praxi/match/MatchTeam.java new file mode 100644 index 0000000..6f9d809 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/MatchTeam.java @@ -0,0 +1,23 @@ +package me.joeleoli.praxi.match; + +import me.joeleoli.nucleus.team.Team; + +public class MatchTeam extends Team { + + public MatchTeam(MatchPlayer matchPlayer) { + super(matchPlayer); + } + + public int getDisconnectedCount() { + int disconnected = 0; + + for (MatchPlayer matchPlayer : this.getTeamPlayers()) { + if (matchPlayer.isDisconnected()) { + disconnected++; + } + } + + return disconnected; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/gui/MatchDetailsMenu.java b/plugin/src/main/java/me/joeleoli/praxi/match/gui/MatchDetailsMenu.java new file mode 100644 index 0000000..c320133 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/gui/MatchDetailsMenu.java @@ -0,0 +1,210 @@ +package me.joeleoli.praxi.match.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.command.CommandHandler; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.menu.buttons.DisplayButton; +import me.joeleoli.nucleus.util.BukkitUtil; +import me.joeleoli.nucleus.util.InventoryUtil; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TimeUtil; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.MatchSnapshot; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +@AllArgsConstructor +public class MatchDetailsMenu extends Menu { + + private MatchSnapshot snapshot; + + @Override + public String getTitle(Player player) { + return ChatColor.YELLOW + "Snapshot of " + this.snapshot.getMatchPlayer().getName(); + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + final ItemStack[] fixedContents = InventoryUtil.fixInventoryOrder(this.snapshot.getContents()); + + for (int i = 0; i < fixedContents.length; i++) { + final ItemStack itemStack = fixedContents[i]; + + if (itemStack == null || itemStack.getType() == Material.AIR) { + continue; + } + + buttons.put(i, new DisplayButton(itemStack, true)); + } + + for (int i = 0; i < this.snapshot.getArmor().length; i++) { + ItemStack itemStack = this.snapshot.getArmor()[i]; + + if (itemStack != null && itemStack.getType() != Material.AIR) { + buttons.put(39 - i, new DisplayButton(itemStack, true)); + } + } + + int pos = 45; + + buttons.put(pos++, new HealthButton(this.snapshot.getHealth())); + buttons.put(pos++, new HungerButton(this.snapshot.getHunger())); + buttons.put(pos++, new EffectsButton(this.snapshot.getEffects())); + + if (this.snapshot.shouldDisplayRemainingPotions()) { + buttons.put( + pos++, + new PotionsButton(this.snapshot.getMatchPlayer().getName(), this.snapshot.getRemainingPotions()) + ); + } + + buttons.put(pos, new StatisticsButton(this.snapshot.getMatchPlayer())); + + if (this.snapshot.getSwitchTo() != null) { + buttons.put(53, new SwitchInventoryButton(this.snapshot.getSwitchTo())); + } + + return buttons; + } + + @Override + public void onOpen(Player player) { + player.sendMessage(Style.YELLOW + "You are viewing " + Style.PINK + this.snapshot.getMatchPlayer().getName() + + Style.YELLOW + "'s inventory."); + } + + @AllArgsConstructor + private class SwitchInventoryButton extends Button { + + private MatchPlayer switchTo; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.LEVER) + .name(Style.YELLOW + Style.BOLD + "Opponent's Inventory") + .lore(Style.YELLOW + "Switch to " + Style.PINK + this.switchTo.getName() + Style.YELLOW + + "'s inventory") + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hb) { + CommandHandler.executeCommand(player, "viewinv " + this.switchTo.getUuid().toString()); + } + + } + + @AllArgsConstructor + private class HealthButton extends Button { + + private int health; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.MELON) + .name(Style.YELLOW + Style.BOLD + "Health: " + Style.PINK + this.health + "/10 " + Style.UNICODE_HEART) + .amount(this.health == 0 ? 1 : this.health) + .build(); + } + + } + + @AllArgsConstructor + private class HungerButton extends Button { + + private int hunger; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.COOKED_BEEF) + .name(Style.YELLOW + Style.BOLD + "Hunger: " + Style.PINK + this.hunger + "/20") + .amount(this.hunger == 0 ? 1 : this.hunger) + .build(); + } + + } + + @AllArgsConstructor + private class EffectsButton extends Button { + + private Collection effects; + + @Override + public ItemStack getButtonItem(Player player) { + final ItemBuilder builder = new ItemBuilder(Material.POTION) + .name(Style.YELLOW + Style.BOLD + "Potion Effects"); + + if (this.effects.isEmpty()) { + builder.lore(Style.PINK + "No potion effects"); + } else { + final List lore = new ArrayList<>(); + + this.effects.forEach(effect -> { + final String name = BukkitUtil.getName(effect.getType()) + " " + (effect.getAmplifier() + 1); + final String duration = " (" + TimeUtil.millisToTimer((effect.getDuration() / 20) * 1000) + ")"; + + lore.add(Style.PINK + name + Style.GRAY + duration); + }); + + builder.lore(lore); + } + + return builder.build(); + } + + } + + @AllArgsConstructor + private class PotionsButton extends Button { + + private String name; + private int potions; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.POTION) + .durability(16421) + .amount(this.potions == 0 ? 1 : this.potions) + .name(Style.YELLOW + Style.BOLD + "Potions") + .lore(Style.PINK + this.name + Style.YELLOW + " had " + Style.PINK + this.potions + Style.YELLOW + + " potion" + (this.potions == 1 ? "" : "s") + " left.") + .build(); + } + + } + + @AllArgsConstructor + private class StatisticsButton extends Button { + + private MatchPlayer matchPlayer; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(Material.PAPER) + .name(Style.YELLOW + Style.BOLD + "Statistics") + .lore(Arrays.asList( + Style.PINK + "Hits: " + Style.RESET + this.matchPlayer.getHits(), + Style.PINK + "Longest Combo: " + Style.RESET + this.matchPlayer.getLongestCombo(), + Style.PINK + "Potions Thrown: " + Style.RESET + this.matchPlayer.getPotionsThrown(), + Style.PINK + "Potions Missed: " + Style.RESET + this.matchPlayer.getPotionsMissed(), + Style.PINK + "Potion Accuracy: " + Style.RESET + this.matchPlayer.getPotionAccuracy() + )) + .build(); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/impl/SoloMatch.java b/plugin/src/main/java/me/joeleoli/praxi/match/impl/SoloMatch.java new file mode 100644 index 0000000..8508beb --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/impl/SoloMatch.java @@ -0,0 +1,375 @@ +package me.joeleoli.praxi.match.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Getter; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.elo.EloUtil; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.MatchSnapshot; +import me.joeleoli.praxi.match.MatchState; +import me.joeleoli.praxi.match.MatchTeam; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.entity.Player; + +@Getter +public class SoloMatch extends Match { + + private MatchPlayer playerA; + private MatchPlayer playerB; + private int playerARoundWins = 0; + private int playerBRoundWins = 0; + private boolean duel; + + public SoloMatch(MatchPlayer playerA, MatchPlayer playerB, Ladder ladder, Arena arena, boolean ranked, + boolean duel) { + this(null, playerA, playerB, ladder, arena, ranked, duel); + + this.duel = duel; + } + + public SoloMatch(UUID queueId, MatchPlayer playerA, MatchPlayer playerB, Ladder ladder, Arena arena, boolean ranked, + boolean duel) { + super(queueId, ladder, arena, ranked); + + this.playerA = playerA; + this.playerB = playerB; + this.duel = duel; + } + + @Override + public boolean isSoloMatch() { + return true; + } + + @Override + public boolean isTeamMatch() { + return false; + } + + @Override + public void onStart() { + if (this.getTotalRoundWins() == 0) { + for (MatchPlayer matchPlayer : new MatchPlayer[]{ this.playerA, this.playerB }) { + final Player player = matchPlayer.toPlayer(); + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setState(PlayerState.IN_MATCH); + praxiPlayer.setMatch(this); + } + } + } + + @Override + public void onEnd() { + final Player winningPlayer = this.getWinningPlayer(); + final Player losingPlayer = this.getOpponentPlayer(winningPlayer); + final MatchPlayer winningMatchPlayer = this.getMatchPlayer(winningPlayer); + final MatchPlayer losingMatchPlayer = this.getMatchPlayer(losingPlayer); + final HoverEvent winnerHoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, HOVER_TEXT); + final ClickEvent winnerClickEvent = + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewinv " + winningPlayer.getUniqueId().toString()); + final HoverEvent loserHoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, HOVER_TEXT); + final ClickEvent loserClickEvent = + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewinv " + losingPlayer.getUniqueId().toString()); + final ChatComponentBuilder inventoriesBuilder = new ChatComponentBuilder(""); + + inventoriesBuilder + .append("Winner: ") + .color(ChatColor.GREEN) + .append(winningPlayer.getName()) + .color(ChatColor.YELLOW); + inventoriesBuilder + .setCurrentHoverEvent(winnerHoverEvent) + .setCurrentClickEvent(winnerClickEvent) + .append(" - ") + .color(ChatColor.GRAY) + .append("Loser: ") + .color(ChatColor.RED) + .append(losingPlayer.getName()) + .color(ChatColor.YELLOW); + inventoriesBuilder + .setCurrentHoverEvent(loserHoverEvent) + .setCurrentClickEvent(loserClickEvent); + + final List components = new ArrayList<>(); + + components.add(new ChatComponentBuilder("").parse("&dPost-Match Inventories &7(click name to view)").create()); + components.add(inventoriesBuilder.create()); + + if (this.isRanked()) { + final int oldWinnerElo = winningMatchPlayer.getElo(); + final int oldLoserElo = losingMatchPlayer.getElo(); + final int newWinnerElo = EloUtil.getNewRating(oldWinnerElo, oldLoserElo, true); + final int newLoserElo = EloUtil.getNewRating(oldLoserElo, oldWinnerElo, false); + + final PraxiPlayer winningPraxiPlayer = PraxiPlayer.getByUuid(winningPlayer.getUniqueId()); + final PraxiPlayer losingPraxiPlayer = PraxiPlayer.getByUuid(losingPlayer.getUniqueId()); + + if (winningPraxiPlayer.isLoaded()) { + winningPraxiPlayer.getStatistics().getLadderStatistics(this.getLadder()).setElo(newWinnerElo); + } + + if (losingPraxiPlayer.isLoaded()) { + losingPraxiPlayer.getStatistics().getLadderStatistics(this.getLadder()).setElo(newLoserElo); + } + + int winnerEloChange = newWinnerElo - oldWinnerElo; + int loserEloChange = oldLoserElo - newLoserElo; + + components.add(new ChatComponentBuilder("") + .parse("&dELO Changes: &a" + winningPlayer.getName() + " +" + winnerEloChange + " (" + + newWinnerElo + ") &c" + losingPlayer.getName() + " -" + loserEloChange + " (" + newLoserElo + + ")") + .create()); + } + + components.add(0, new ChatComponentBuilder("").parse(Style.getBorderLine()).create()); + components.add(new ChatComponentBuilder("").parse(Style.getBorderLine()).create()); + + for (Player player : new Player[]{ winningPlayer, losingPlayer }) { + components.forEach(player::sendMessage); + } + + for (Player player : this.getSpectators()) { + components.forEach(player::sendMessage); + } + } + + @Override + public boolean canEnd() { + if (this.getLadder().isSumo()) { + return this.playerA.isDisconnected() + || this.playerB.isDisconnected() + || (this.isRanked() ? (this.playerARoundWins == 3 || this.playerBRoundWins == 3) + : (this.playerARoundWins == 1 || this.playerBRoundWins == 1)); + } else { + return !this.playerA.isAlive() || !this.playerB.isAlive(); + } + } + + @Override + public Player getWinningPlayer() { + if (this.playerA.isDisconnected() || !this.playerA.isAlive()) { + return this.playerB.toPlayer(); + } else { + return this.playerA.toPlayer(); + } + } + + @Override + public MatchTeam getWinningTeam() { + throw new UnsupportedOperationException("Cannot get winning team from a SoloMatch"); + } + + @Override + public MatchPlayer getMatchPlayerA() { + return this.playerA; + } + + @Override + public MatchPlayer getMatchPlayerB() { + return this.playerB; + } + + @Override + public List getMatchPlayers() { + throw new UnsupportedOperationException("Cannot get match players from a SoloMatch"); + } + + @Override + public Player getPlayerA() { + return this.playerA.toPlayer(); + } + + @Override + public Player getPlayerB() { + return this.playerB.toPlayer(); + } + + @Override + public List getPlayers() { + final List players = new ArrayList<>(); + + final Player playerA = this.playerA.toPlayer(); + final Player playerB = this.playerB.toPlayer(); + + if (playerA != null) { + players.add(playerA); + } + + if (playerB != null) { + players.add(playerB); + } + + return players; + } + + @Override + public MatchTeam getTeamA() { + throw new UnsupportedOperationException("Cannot get team from a SoloMatch"); + } + + @Override + public MatchTeam getTeamB() { + throw new UnsupportedOperationException("Cannot get team from a SoloMatch"); + } + + @Override + public MatchTeam getTeam(MatchPlayer matchPlayer) { + throw new UnsupportedOperationException("Cannot get team from a SoloMatch"); + } + + @Override + public MatchTeam getTeam(Player player) { + throw new UnsupportedOperationException("Cannot get team from a SoloMatch"); + } + + @Override + public MatchPlayer getMatchPlayer(Player player) { + if (this.playerA.getUuid().equals(player.getUniqueId())) { + return this.playerA; + } else if (this.playerB.getUuid().equals(player.getUniqueId())) { + return this.playerB; + } else { + return null; + } + } + + @Override + public int getOpponentsLeft(Player player) { + throw new UnsupportedOperationException("Cannot get opponents left from a SoloMatch"); + } + + @Override + public MatchTeam getOpponentTeam(MatchTeam team) { + throw new UnsupportedOperationException("Cannot get opponent team from a SoloMatch"); + } + + @Override + public MatchTeam getOpponentTeam(Player player) { + throw new UnsupportedOperationException("Cannot get opponent team from a SoloMatch"); + } + + @Override + public Player getOpponentPlayer(Player player) { + if (player == null) { + return null; + } + + if (this.playerA.getUuid().equals(player.getUniqueId())) { + return this.playerB.toPlayer(); + } else if (this.playerB.getUuid().equals(player.getUniqueId())) { + return this.playerA.toPlayer(); + } else { + return null; + } + } + + @Override + public MatchPlayer getOpponentMatchPlayer(Player player) { + if (this.playerA.getUuid().equals(player.getUniqueId())) { + return this.playerB; + } else if (this.playerB.getUuid().equals(player.getUniqueId())) { + return this.playerA; + } else { + return null; + } + } + + @Override + public int getTotalRoundWins() { + return this.playerARoundWins + this.playerBRoundWins; + } + + @Override + public int getRoundWins(MatchPlayer matchPlayer) { + if (this.playerA.equals(matchPlayer)) { + return this.playerARoundWins; + } else if (this.playerB.equals(matchPlayer)) { + return this.playerBRoundWins; + } else { + return -1; + } + } + + @Override + public int getRoundWins(MatchTeam matchTeam) { + throw new UnsupportedOperationException("Cannot get team round wins from SoloMatch"); + } + + @Override + public int getRoundsNeeded(MatchPlayer matchPlayer) { + if (this.playerA.equals(matchPlayer)) { + return 3 - this.playerARoundWins; + } else if (this.playerB.equals(matchPlayer)) { + return 3 - this.playerBRoundWins; + } else { + return -1; + } + } + + @Override + public int getRoundsNeeded(MatchTeam matchTeam) { + throw new UnsupportedOperationException("Cannot get team round wins from SoloMatch"); + } + + @Override + public void onDeath(Player player, Player killer) { + MatchPlayer roundLoser = this.getMatchPlayer(player); + MatchPlayer roundWinner = this.getOpponentMatchPlayer(player); + + this.getSnapshots().add(new MatchSnapshot(roundLoser, roundWinner)); + + PlayerUtil.reset(player); + + if (this.getLadder().isSumo()) { + if (this.playerA.getUuid().equals(player.getUniqueId())) { + this.playerBRoundWins++; + } else { + this.playerARoundWins++; + } + + if (this.canEnd()) { + final String broadcast = + Style.PINK + roundWinner.getName() + Style.YELLOW + " has " + Style.GREEN + "won" + + Style.YELLOW + " the match."; + + this.setState(MatchState.ENDING); + this.broadcastMessage(broadcast); + this.getOpponentPlayer(player).hidePlayer(player); + this.getSpectators().forEach(other -> other.hidePlayer(player)); + } else { + final String broadcast = + Style.PINK + roundWinner.getName() + Style.YELLOW + " has " + Style.GREEN + "won" + + Style.YELLOW + " the round, they need " + Style.GOLD + this.getRoundsNeeded(roundWinner) + + Style.YELLOW + " more to win."; + + this.broadcastMessage(broadcast); + this.handleStart(); + } + } + } + + @Override + public void onRespawn(Player player) { + if (this.getLadder().isSumo() && !this.isEnding()) { + player.teleport(this.getArena().getSpawn1()); + this.getOpponentPlayer(player).teleport(this.getArena().getSpawn2()); + } else { + player.teleport(player.getLocation().clone().add(0, 3, 0)); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/match/impl/TeamMatch.java b/plugin/src/main/java/me/joeleoli/praxi/match/impl/TeamMatch.java new file mode 100644 index 0000000..26a70a7 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/match/impl/TeamMatch.java @@ -0,0 +1,446 @@ +package me.joeleoli.praxi.match.impl; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.MatchSnapshot; +import me.joeleoli.praxi.match.MatchState; +import me.joeleoli.praxi.match.MatchTeam; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.entity.Player; + +@Getter +public class TeamMatch extends Match { + + private MatchTeam teamA; + private MatchTeam teamB; + private int teamARoundWins = 0; + private int teamBRoundWins = 0; + + public TeamMatch(MatchTeam teamA, MatchTeam teamB, Ladder ladder, Arena arena) { + super(null, ladder, arena, false); + + this.teamA = teamA; + this.teamB = teamB; + } + + @Override + public boolean isDuel() { + return false; + } + + @Override + public boolean isSoloMatch() { + return false; + } + + @Override + public boolean isTeamMatch() { + return true; + } + + @Override + public void onStart() { + } + + @Override + public void onEnd() { + final MatchTeam winningTeam = this.getWinningTeam(); + final MatchTeam losingTeam = this.getOpponentTeam(winningTeam); + final ChatComponentBuilder winnerInventories = new ChatComponentBuilder(""); + final ChatComponentBuilder loserInventories = new ChatComponentBuilder(""); + + winnerInventories + .append("Winners: ") + .color(ChatColor.GREEN); + loserInventories + .append("Losers: ") + .color(ChatColor.RED); + + for (MatchPlayer matchPlayer : winningTeam.getTeamPlayers()) { + final HoverEvent hover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, HOVER_TEXT); + final ClickEvent click = + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewinv " + matchPlayer.getUuid().toString()); + + winnerInventories + .append(matchPlayer.getName()) + .color(ChatColor.YELLOW); + winnerInventories + .setCurrentHoverEvent(hover) + .setCurrentClickEvent(click) + .append(", ") + .color(ChatColor.YELLOW); + } + + for (MatchPlayer matchPlayer : losingTeam.getTeamPlayers()) { + final HoverEvent hover = new HoverEvent(HoverEvent.Action.SHOW_TEXT, HOVER_TEXT); + final ClickEvent click = + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/viewinv " + matchPlayer.getUuid().toString()); + + loserInventories + .append(matchPlayer.getName()) + .color(ChatColor.YELLOW); + loserInventories + .setCurrentHoverEvent(hover) + .setCurrentClickEvent(click) + .append(", ") + .color(ChatColor.YELLOW); + } + + winnerInventories.getCurrent().setText(winnerInventories.getCurrent().getText().substring( + 0, + winnerInventories.getCurrent().getText().length() - 2 + )); + loserInventories.getCurrent().setText(loserInventories.getCurrent().getText().substring( + 0, + loserInventories.getCurrent().getText().length() - 2 + )); + + final List components = new ArrayList<>(); + + components.add(new ChatComponentBuilder("").parse(Style.getBorderLine()).create()); + components.add(new ChatComponentBuilder("").parse("&dPost-Match Inventories &7(click name to view)").create()); + components.add(winnerInventories.create()); + components.add(loserInventories.create()); + components.add(new ChatComponentBuilder("").parse(Style.getBorderLine()).create()); + + for (Player player : this.getPlayers()) { + components.forEach(player::sendMessage); + } + + for (Player player : this.getSpectators()) { + components.forEach(player::sendMessage); + } + } + + @Override + public boolean canEnd() { + if (this.getLadder().isSumo()) { + return (this.teamA.getDeadCount() + this.teamA.getDisconnectedCount()) >= this.teamA.getTeamPlayers().size() + || + (this.teamB.getDeadCount() + this.teamB.getDisconnectedCount()) >= this.teamB.getTeamPlayers().size() + || this.teamARoundWins == 3 + || this.teamBRoundWins == 3; + } else { + return this.teamA.getAliveTeamPlayers().isEmpty() || this.teamB.getAliveTeamPlayers().isEmpty(); + } + } + + @Override + public Player getWinningPlayer() { + throw new UnsupportedOperationException("Cannot get solo winning player from a TeamMatch"); + } + + @Override + public MatchTeam getWinningTeam() { + if (this.getLadder().isSumo()) { + if (this.teamA.getDisconnectedCount() == this.teamA.getTeamPlayers().size()) { + return this.teamB; + } else if (this.teamB.getDisconnectedCount() == this.teamB.getTeamPlayers().size()) { + return this.teamA; + } + + return this.teamARoundWins == 3 ? this.teamA : this.teamB; + } else { + if (this.teamA.getAliveTeamPlayers().isEmpty()) { + return this.teamB; + } else if (this.teamB.getAliveTeamPlayers().isEmpty()) { + return this.teamA; + } else { + return null; + } + } + } + + @Override + public MatchPlayer getMatchPlayerA() { + throw new UnsupportedOperationException("Cannot get solo match player from a TeamMatch"); + } + + @Override + public MatchPlayer getMatchPlayerB() { + throw new UnsupportedOperationException("Cannot get solo match player from a TeamMatch"); + } + + @Override + public List getMatchPlayers() { + List matchPlayers = new ArrayList<>(); + + matchPlayers.addAll(this.teamA.getTeamPlayers()); + matchPlayers.addAll(this.teamB.getTeamPlayers()); + + return matchPlayers; + } + + @Override + public Player getPlayerA() { + throw new UnsupportedOperationException("Cannot get solo player from a TeamMatch"); + } + + @Override + public Player getPlayerB() { + throw new UnsupportedOperationException("Cannot get solo player from a TeamMatch"); + } + + @Override + public List getPlayers() { + List players = new ArrayList<>(); + + this.teamA.getTeamPlayers().forEach(matchPlayer -> { + Player player = matchPlayer.toPlayer(); + + if (player != null) { + players.add(player); + } + }); + + this.teamB.getTeamPlayers().forEach(matchPlayer -> { + Player player = matchPlayer.toPlayer(); + + if (player != null) { + players.add(player); + } + }); + + return players; + } + + @Override + public MatchTeam getTeamA() { + return this.teamA; + } + + @Override + public MatchTeam getTeamB() { + return this.teamB; + } + + @Override + public MatchTeam getTeam(MatchPlayer matchPlayer) { + for (MatchPlayer teamMatchPlayer : this.teamA.getTeamPlayers()) { + if (teamMatchPlayer.getUuid().equals(matchPlayer.getUuid())) { + return this.teamA; + } + } + + for (MatchPlayer teamMatchPlayer : this.teamB.getTeamPlayers()) { + if (teamMatchPlayer.getUuid().equals(matchPlayer.getUuid())) { + return this.teamB; + } + } + + return null; + } + + @Override + public MatchTeam getTeam(Player player) { + for (MatchPlayer teamMatchPlayer : this.teamA.getTeamPlayers()) { + if (teamMatchPlayer.getUuid().equals(player.getUniqueId())) { + return this.teamA; + } + } + + for (MatchPlayer teamMatchPlayer : this.teamB.getTeamPlayers()) { + if (teamMatchPlayer.getUuid().equals(player.getUniqueId())) { + return this.teamB; + } + } + + return null; + } + + @Override + public MatchPlayer getMatchPlayer(Player player) { + for (MatchPlayer matchPlayer : this.teamA.getTeamPlayers()) { + if (matchPlayer.getUuid().equals(player.getUniqueId())) { + return matchPlayer; + } + } + + for (MatchPlayer matchPlayer : this.teamB.getTeamPlayers()) { + if (matchPlayer.getUuid().equals(player.getUniqueId())) { + return matchPlayer; + } + } + + return null; + } + + @Override + public int getOpponentsLeft(Player player) { + if (this.teamA.containsPlayer(player)) { + return this.teamB.getAliveCount() - this.teamB.getDisconnectedCount(); + } else if (this.teamB.containsPlayer(player)) { + return this.teamA.getAliveCount() - this.teamA.getDisconnectedCount(); + } else { + return -1; + } + } + + @Override + public MatchTeam getOpponentTeam(MatchTeam team) { + if (this.teamA.equals(team)) { + return this.teamB; + } else if (this.teamB.equals(team)) { + return this.teamA; + } else { + return null; + } + } + + @Override + public MatchTeam getOpponentTeam(Player player) { + if (this.teamA.containsPlayer(player)) { + return this.teamB; + } else if (this.teamB.containsPlayer(player)) { + return this.teamA; + } else { + return null; + } + } + + @Override + public Player getOpponentPlayer(Player player) { + throw new UnsupportedOperationException("Cannot get solo opponent player from TeamMatch"); + } + + @Override + public MatchPlayer getOpponentMatchPlayer(Player player) { + throw new UnsupportedOperationException("Cannot get solo opponent match player from TeamMatch"); + } + + @Override + public int getTotalRoundWins() { + return this.teamARoundWins + this.teamBRoundWins; + } + + @Override + public int getRoundWins(MatchPlayer matchPlayer) { + throw new UnsupportedOperationException("Cannot get solo round wins from TeamMatch"); + } + + @Override + public int getRoundWins(MatchTeam matchTeam) { + if (this.teamA.equals(matchTeam)) { + return this.teamARoundWins; + } else if (this.teamB.equals(matchTeam)) { + return this.teamBRoundWins; + } else { + return -1; + } + } + + @Override + public int getRoundsNeeded(MatchPlayer matchPlayer) { + throw new UnsupportedOperationException("Cannot get solo rounds needed from TeamMatch"); + } + + @Override + public int getRoundsNeeded(MatchTeam matchTeam) { + if (this.teamA.equals(matchTeam)) { + return 3 - this.teamARoundWins; + } else if (this.teamB.equals(matchTeam)) { + return 3 - this.teamBRoundWins; + } else { + return -1; + } + } + + @Override + public void onDeath(Player player, Player killer) { + // TODO: request teams messages directly then request global messages to spectators + // MatchTeam roundLoser = this.getOpponentTeam(player); + + this.getSnapshots().add(new MatchSnapshot(this.getMatchPlayer(player))); + + PlayerUtil.reset(player); + + this.getPlayers().forEach(matchPlayer -> { + matchPlayer.hidePlayer(player); + }); + + if (this.getLadder().isSumo()) { + final MatchTeam deadTeam = this.getTeam(player); + final MatchTeam roundWinner = this.getOpponentTeam(deadTeam); + final int dead = deadTeam.getDisconnectedCount() + deadTeam.getDeadCount(); + + if (dead == deadTeam.getTeamPlayers().size()) { + if (this.teamA.equals(roundWinner)) { + this.teamARoundWins++; + } else { + this.teamBRoundWins++; + } + + if (this.canEnd()) { + this.setState(MatchState.ENDING); + this.getPlayers().forEach(other -> other.hidePlayer(player)); + this.getSpectators().forEach(other -> other.hidePlayer(player)); + } else { + final String broadcast = + roundWinner.getLeader().getDisplayName() + Style.YELLOW + "'s team has " + Style.GREEN + + "won" + Style.YELLOW + " the round, they need " + Style.GOLD + + this.getRoundsNeeded(roundWinner) + Style.YELLOW + " more to win."; + + this.broadcastMessage(broadcast); + this.handleStart(); + } + } else { + for (Player other : this.getPlayers()) { + other.hidePlayer(player); + } + + player.setAllowFlight(true); + player.setFlying(true); + player.updateInventory(); + } + } else { + if (!this.canEnd()) { + player.setAllowFlight(true); + player.setFlying(true); + player.updateInventory(); + } + } + } + + @Override + public void onRespawn(Player player) { + if (this.getLadder().isSumo() && !this.isEnding()) { + for (MatchPlayer matchPlayer : this.teamA.getTeamPlayers()) { + if (matchPlayer.isDisconnected()) { + continue; + } + + final Player toPlayer = matchPlayer.toPlayer(); + + if (toPlayer != null && toPlayer.isOnline()) { + toPlayer.teleport(this.getArena().getSpawn1()); + } + } + + for (MatchPlayer matchPlayer : this.teamB.getTeamPlayers()) { + if (matchPlayer.isDisconnected()) { + continue; + } + + final Player toPlayer = matchPlayer.toPlayer(); + + if (toPlayer != null && toPlayer.isOnline()) { + toPlayer.teleport(this.getArena().getSpawn2()); + } + } + } else { + player.teleport(player.getLocation().clone().add(0, 3, 0)); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/mongo/PraxiMongo.java b/plugin/src/main/java/me/joeleoli/praxi/mongo/PraxiMongo.java new file mode 100644 index 0000000..67d5dd0 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/mongo/PraxiMongo.java @@ -0,0 +1,66 @@ +package me.joeleoli.praxi.mongo; + +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.ReplaceOptions; +import java.util.Collections; +import java.util.UUID; +import me.joeleoli.nucleus.config.ConfigCursor; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bson.Document; + +public class PraxiMongo { + + private MongoClient client; + private MongoDatabase database; + private MongoCollection players; + private MongoCollection matches; + + public PraxiMongo() { + ConfigCursor cursor = new ConfigCursor(Praxi.getInstance().getMainConfig(), "mongo"); + + if (!cursor.exists("host") + || !cursor.exists("port") + || !cursor.exists("database") + || !cursor.exists("authentication.enabled") + || !cursor.exists("authentication.username") + || !cursor.exists("authentication.password") + || !cursor.exists("authentication.database")) { + throw new RuntimeException("Missing configuration option"); + } + + if (cursor.getBoolean("authentication.enabled")) { + final MongoCredential credential = MongoCredential.createCredential( + cursor.getString("authentication.username"), + cursor.getString("authentication.database"), + cursor.getString("authentication.password").toCharArray() + ); + + this.client = new MongoClient(new ServerAddress(cursor.getString("host"), cursor.getInt("port")), + Collections.singletonList(credential) + ); + } else { + this.client = new MongoClient(new ServerAddress(cursor.getString("host"), cursor.getInt("port"))); + } + + this.database = this.client.getDatabase("praxi"); + this.players = this.database.getCollection("players"); + this.matches = this.database.getCollection("matches"); + } + + public Document getPlayer(UUID uuid) { + return this.players.find(Filters.eq("uuid", uuid.toString())).first(); + } + + public void replacePlayer(PraxiPlayer player, Document document) { + this.players.replaceOne(Filters.eq("uuid", player.getUuid().toString()), document, + new ReplaceOptions().upsert(true) + ); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/Party.java b/plugin/src/main/java/me/joeleoli/praxi/party/Party.java new file mode 100644 index 0000000..f90c766 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/Party.java @@ -0,0 +1,154 @@ +package me.joeleoli.praxi.party; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.team.Team; +import me.joeleoli.nucleus.team.TeamPlayer; +import me.joeleoli.nucleus.util.ObjectUtil; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.player.PraxiPlayer; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.entity.Player; + +@Getter +public class Party extends Team { + + @Getter + private static List parties = new ArrayList<>(); + + private PartyState state = PartyState.CLOSED; + private Map invited; + @Setter + private PartyEvent selectedEvent; + + public Party(Player player) { + super(new TeamPlayer(player.getUniqueId(), player.getName())); + + this.invited = new HashMap<>(); + + parties.add(this); + } + + public void setState(PartyState state) { + this.state = state; + + this.broadcast(Style.YELLOW + "The party state has been changed to: " + Style.RESET + this.state.name()); + } + + public boolean canInvite(Player player) { + for (UUID uuid : this.invited.keySet()) { + if (uuid.equals(player.getUniqueId())) { + if (System.currentTimeMillis() - this.invited.get(uuid) >= 30_000) { + this.invited.remove(uuid); + return true; + } + + return false; + } + } + + return true; + } + + public boolean isInvited(Player player) { + for (UUID uuid : this.invited.keySet()) { + if (uuid.equals(player.getUniqueId())) { + if (System.currentTimeMillis() - this.invited.get(uuid) >= 30_000) { + this.invited.remove(uuid); + return false; + } + + return true; + } + } + + return false; + } + + public void invite(Player target) { + this.invited.put(target.getUniqueId(), System.currentTimeMillis()); + + final HoverEvent hoverEvent = new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new ChatComponentBuilder(Style.YELLOW + "Click to join the party.").create() + ); + final ClickEvent clickEvent = + new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/party join " + this.getLeader().getName()); + + this.broadcast(Style.RESET + target.getDisplayName() + " " + Style.YELLOW + "has been invited to the party."); + + target.sendMessage( + Style.YELLOW + "You have been invited to join " + Style.RESET + this.getLeader().getDisplayName() + + Style.YELLOW + "'s party."); + target.sendMessage(new ChatComponentBuilder("").parse(Style.GOLD + "Click here to join the party.") + .attachToEachPart(clickEvent).attachToEachPart(hoverEvent) + .create()); + } + + public void join(Player player) { + this.getTeamPlayers().add(new TeamPlayer(player.getUniqueId(), player.getName())); + this.invited.keySet().removeIf(uuid -> uuid.equals(player.getUniqueId())); + this.broadcast(Style.RESET + player.getDisplayName() + Style.YELLOW + " has joined the party."); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setParty(this); + praxiPlayer.loadHotbar(); + } + + public void leave(Player player, boolean kick) { + this.broadcast( + Style.RESET + player.getDisplayName() + Style.YELLOW + " has " + (kick ? "been kicked" : "left") + + " the party."); + this.getTeamPlayers().removeIf(playerInfo -> playerInfo.getUuid().equals(player.getUniqueId())); + + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setParty(null); + praxiPlayer.loadHotbar(); + } + + public void disband() { + parties.remove(this); + + this.broadcast(Style.YELLOW + "The party has been disbanded."); + + this.getPlayers().forEach(player -> { + PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.setParty(null); + + if (praxiPlayer.isInLobby()) { + praxiPlayer.loadHotbar(); + } + }); + } + + public void sendInformation(Player player) { + StringBuilder builder = new StringBuilder(); + + for (Player member : this.getPlayers()) { + builder.append(member.getName()); + builder.append(", "); + } + + final String[] lines = new String[]{ + Style.getBorderLine(), + Style.GOLD + "Party of " + this.getLeader().getName(), + Style.YELLOW + "State: " + Style.GRAY + ObjectUtil.toReadable(this.state), + Style.YELLOW + "Members: " + Style.GRAY + + builder.toString().substring(0, builder.toString().length() - 2), + Style.getBorderLine() + }; + + player.sendMessage(lines); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/PartyEvent.java b/plugin/src/main/java/me/joeleoli/praxi/party/PartyEvent.java new file mode 100644 index 0000000..d1390a3 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/PartyEvent.java @@ -0,0 +1,15 @@ +package me.joeleoli.praxi.party; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum PartyEvent { + + FFA("FFA"), + SPLIT("Split"); + + private String name; + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/PartyState.java b/plugin/src/main/java/me/joeleoli/praxi/party/PartyState.java new file mode 100644 index 0000000..6de19e9 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/PartyState.java @@ -0,0 +1,6 @@ +package me.joeleoli.praxi.party; + +public enum PartyState { + OPEN, + CLOSED +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/gui/OtherPartiesMenu.java b/plugin/src/main/java/me/joeleoli/praxi/party/gui/OtherPartiesMenu.java new file mode 100644 index 0000000..f2d4bc6 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/gui/OtherPartiesMenu.java @@ -0,0 +1,30 @@ +package me.joeleoli.praxi.party.gui; + +import java.util.HashMap; +import java.util.Map; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.pagination.PaginatedMenu; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.party.Party; +import me.joeleoli.praxi.party.gui.button.PartyDisplayButton; +import org.bukkit.entity.Player; + +public class OtherPartiesMenu extends PaginatedMenu { + + @Override + public String getPrePaginatedTitle(Player player) { + return Style.GOLD + "Other Parties"; + } + + @Override + public Map getAllPagesButtons(Player player) { + Map buttons = new HashMap<>(); + + Party.getParties().forEach(party -> { + buttons.put(buttons.size(), new PartyDisplayButton(party)); + }); + + return buttons; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectEventMenu.java b/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectEventMenu.java new file mode 100644 index 0000000..1759ef3 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectEventMenu.java @@ -0,0 +1,68 @@ +package me.joeleoli.praxi.party.gui; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.party.PartyEvent; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class PartyEventSelectEventMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.BLUE + Style.BOLD + "Select an event"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + buttons.put(3, new SelectEventButton(PartyEvent.FFA)); + buttons.put(5, new SelectEventButton(PartyEvent.SPLIT)); + + return buttons; + } + + @AllArgsConstructor + private class SelectEventButton extends Button { + + private PartyEvent partyEvent; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(this.partyEvent == PartyEvent.FFA ? Material.QUARTZ : Material.REDSTONE) + .name(Style.GREEN + Style.BOLD + this.partyEvent.getName()) + .lore(Arrays.asList( + "", + Style.YELLOW + "Click here to select " + Style.GREEN + Style.BOLD + + this.partyEvent.getName() + Style.YELLOW + "." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You are not in a party."); + return; + } + + praxiPlayer.getParty().setSelectedEvent(this.partyEvent); + + new PartyEventSelectLadderMenu().openMenu(player); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectLadderMenu.java b/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectLadderMenu.java new file mode 100644 index 0000000..301e747 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/gui/PartyEventSelectLadderMenu.java @@ -0,0 +1,151 @@ +package me.joeleoli.praxi.party.gui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.MatchTeam; +import me.joeleoli.praxi.match.impl.TeamMatch; +import me.joeleoli.praxi.party.Party; +import me.joeleoli.praxi.party.PartyEvent; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class PartyEventSelectLadderMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.GOLD + Style.BOLD + "Select a ladder"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + for (Ladder ladder : Ladder.getLadders()) { + if (ladder.isEnabled()) { + buttons.put(buttons.size(), new SelectLadderButton(ladder)); + } + } + + return buttons; + } + + @Override + public void onClose(Player player) { + if (!this.isClosedByMenu()) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() != null) { + praxiPlayer.getParty().setSelectedEvent(null); + } + } + } + + @AllArgsConstructor + private class SelectLadderButton extends Button { + + private Ladder ladder; + + @Override + public ItemStack getButtonItem(Player player) { + return new ItemBuilder(this.ladder.getDisplayIcon()) + .name(Style.PINK + Style.BOLD + this.ladder.getName()) + .lore(Arrays.asList( + "", + Style.YELLOW + "Click here to select " + Style.PINK + Style.BOLD + + this.ladder.getName() + Style.YELLOW + "." + )) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hbSlot) { + Menu.currentlyOpenedMenus.get(player.getName()).setClosedByMenu(true); + + player.closeInventory(); + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer.getParty() == null) { + player.sendMessage(Style.RED + "You are not in a party."); + return; + } + + if (praxiPlayer.getParty().getSelectedEvent() == null) { + return; + } + + if (praxiPlayer.getParty().getTeamPlayers().size() <= 1) { + player.sendMessage(Style.RED + "You do not have enough players in your party to start an event."); + return; + } + + Party party = praxiPlayer.getParty(); + Arena arena = Arena.getRandom(this.ladder); + + if (arena == null) { + player.sendMessage(Style.RED + "There are no available arenas."); + return; + } + + arena.setActive(true); + + Match match; + + if (party.getSelectedEvent() == PartyEvent.FFA) { + player.sendMessage(Style.RED + "The FFA party event is currently disabled."); + return; + } else { + MatchTeam teamA = new MatchTeam(new MatchPlayer(party.getLeader().toPlayer())); + MatchTeam teamB = new MatchTeam(new MatchPlayer(party.getPlayers().get(1))); + + final List players = new ArrayList<>(); + + players.addAll(party.getPlayers()); + + Collections.shuffle(players); + + // Create match + match = new TeamMatch(teamA, teamB, this.ladder, arena); + + for (Player other : players) { + final PraxiPlayer otherData = PraxiPlayer.getByUuid(other.getUniqueId()); + + otherData.setState(PlayerState.IN_MATCH); + otherData.setMatch(match); + + if (teamA.getLeader().getUuid().equals(other.getUniqueId()) || + teamB.getLeader().getUuid().equals(other.getUniqueId())) { + continue; + } + + if (teamA.getTeamPlayers().size() > teamB.getTeamPlayers().size()) { + teamB.getTeamPlayers().add(new MatchPlayer(other)); + } else { + teamA.getTeamPlayers().add(new MatchPlayer(other)); + } + } + } + + // Start match + match.handleStart(); + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/party/gui/button/PartyDisplayButton.java b/plugin/src/main/java/me/joeleoli/praxi/party/gui/button/PartyDisplayButton.java new file mode 100644 index 0000000..6412642 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/party/gui/button/PartyDisplayButton.java @@ -0,0 +1,47 @@ +package me.joeleoli.praxi.party.gui.button; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.team.TeamPlayer; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.party.Party; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +@AllArgsConstructor +public class PartyDisplayButton extends Button { + + private Party party; + + @Override + public ItemStack getButtonItem(Player player) { + final List lore = new ArrayList<>(); + int added = 0; + + for (TeamPlayer teamPlayer : this.party.getTeamPlayers()) { + if (added >= 10) { + break; + } + + lore.add(Style.GRAY + " - " + Style.RESET + teamPlayer.getDisplayName()); + + added++; + } + + if (this.party.getTeamPlayers().size() != added) { + lore.add(Style.GRAY + " and " + (this.party.getTeamPlayers().size() - added) + " others..."); + } + + return new ItemBuilder(Material.SKULL_ITEM) + .amount(this.party.getTeamPlayers().size()) + .durability(3) + .name(Style.GOLD + this.party.getLeader().getName() + "s Party") + .lore(lore) + .build(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/KitEditor.java b/plugin/src/main/java/me/joeleoli/praxi/player/KitEditor.java new file mode 100644 index 0000000..d83ba8c --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/KitEditor.java @@ -0,0 +1,25 @@ +package me.joeleoli.praxi.player; + +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.praxi.kit.NamedKit; +import me.joeleoli.praxi.ladder.Ladder; + +@Setter +public class KitEditor { + + @Getter + private boolean active; + private boolean rename; + @Getter + private PlayerState previousState; + @Getter + private Ladder selectedLadder; + @Getter + private NamedKit selectedKit; + + public boolean isRenaming() { + return this.active && this.rename && this.selectedKit != null; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/LadderStatistics.java b/plugin/src/main/java/me/joeleoli/praxi/player/LadderStatistics.java new file mode 100644 index 0000000..bb168ba --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/LadderStatistics.java @@ -0,0 +1,19 @@ +package me.joeleoli.praxi.player; + +import lombok.Data; + +@Data +public class LadderStatistics { + + private int elo = 1000; + private int won, lost; + + public void incrementWon() { + this.won++; + } + + public void incrementLost() { + this.lost++; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/PlayerHotbar.java b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerHotbar.java new file mode 100644 index 0000000..c369d11 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerHotbar.java @@ -0,0 +1,231 @@ +package me.joeleoli.praxi.player; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import lombok.AllArgsConstructor; +import lombok.Getter; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +public class PlayerHotbar { + + @Getter + private static Map items = new HashMap<>(); + + // Utility class - cannot be instantiated + private PlayerHotbar() { + } + + public static void init() { + items.put( + HotbarItem.QUEUE_JOIN_UNRANKED, + new ItemBuilder(Material.IRON_SWORD).name(Style.GRAY + Style.BOLD + "Unranked Queue") + .lore(Style.YELLOW + "Right-click to join an unranked queue.") + .build() + ); + items.put( + HotbarItem.QUEUE_JOIN_RANKED, + new ItemBuilder(Material.DIAMOND_SWORD).name(Style.GREEN + Style.BOLD + "Ranked Queue") + .lore(Style.YELLOW + "Right-click to join a ranked queue.") + .build() + ); + items.put( + HotbarItem.QUEUE_LEAVE, + new ItemBuilder(Material.INK_SACK).durability(1).name(Style.RED + Style.BOLD + "Leave Queue") + .lore(Style.YELLOW + "Right-click to leave your queue.").build() + ); + items.put( + HotbarItem.PARTY_EVENTS, + new ItemBuilder(Material.DIAMOND_SWORD).name(Style.GREEN + Style.BOLD + "Party Events") + .lore(Style.YELLOW + "Right-click to start a party event.") + .build() + ); + items.put( + HotbarItem.PARTY_CREATE, + new ItemBuilder(Material.NAME_TAG).name(Style.YELLOW + Style.BOLD + "Create Party") + .lore(Style.YELLOW + "Right-click to create a party.").build() + ); + items.put( + HotbarItem.PARTY_DISBAND, + new ItemBuilder(Material.INK_SACK).durability(1).name(Style.RED + Style.BOLD + "Disband Party") + .lore(Style.YELLOW + "Right-click to disband your party.").build() + ); + items.put( + HotbarItem.PARTY_LEAVE, + new ItemBuilder(Material.INK_SACK).durability(1).name(Style.RED + Style.BOLD + "Leave Party") + .lore(Style.YELLOW + "Right-click to leave your party.").build() + ); + items.put( + HotbarItem.PARTY_INFORMATION, + new ItemBuilder(Material.SKULL_ITEM).durability(3).name(Style.YELLOW + Style.BOLD + "Party Information") + .lore(Style.YELLOW + + "Right-click to show your party's information.").build() + ); + items.put( + HotbarItem.OTHER_PARTIES, + new ItemBuilder(Material.CHEST).name(Style.BLUE + Style.BOLD + "Other Parties") + .lore(Style.YELLOW + "Right-click to show other parties.").build() + ); + items.put(HotbarItem.SETTINGS, new ItemBuilder(Material.WATCH).name(Style.PINK + Style.BOLD + "Settings") + .lore(Style.YELLOW + + "Right-click to open your settings.") + .build()); + items.put(HotbarItem.KIT_EDITOR, new ItemBuilder(Material.BOOK).name(Style.RED + Style.BOLD + "Kit Editor") + .lore(Style.YELLOW + + "Right-click to open the kit editor.") + .build()); + items.put( + HotbarItem.SPECTATE_STOP, + new ItemBuilder(Material.INK_SACK).durability(1).name(Style.RED + Style.BOLD + "Stop Spectating") + .lore(Style.YELLOW + "Right-click to stop spectating.").build() + ); + items.put( + HotbarItem.VIEW_INVENTORY, + new ItemBuilder(Material.BOOK).name(Style.GOLD + Style.BOLD + "View Inventory") + .lore(Style.YELLOW + "Right-click a player to view their inventory.") + .build() + ); + items.put( + HotbarItem.EVENT_JOIN, + new ItemBuilder(Material.NETHER_STAR).name(Style.AQUA + Style.BOLD + "Join Event") + .lore(Style.YELLOW + "Right-click to join the event.").build() + ); + items.put( + HotbarItem.EVENT_LEAVE, + new ItemBuilder(Material.NETHER_STAR).name(Style.RED + Style.BOLD + "Leave Event") + .lore(Style.YELLOW + "Right-click to leave the event.").build() + ); + items.put( + HotbarItem.REMATCH_REQUEST, + new ItemBuilder(Material.EMERALD).name(Style.DARK_GREEN + Style.BOLD + "Request Rematch") + .lore(Style.YELLOW + "Right-click to request a rematch.").build() + ); + items.put( + HotbarItem.REMATCH_ACCEPT, + new ItemBuilder(Material.DIAMOND).name(Style.AQUA + Style.BOLD + "Accept Rematch") + .lore(Style.YELLOW + "Right-click to accept a rematch.").build() + ); + } + + public static ItemStack[] getLayout(HotbarLayout layout, PraxiPlayer praxiPlayer) { + final ItemStack[] toReturn = new ItemStack[9]; + + Arrays.fill(toReturn, null); + + switch (layout) { + case LOBBY: { + if (praxiPlayer.getParty() == null) { + toReturn[0] = items.get(HotbarItem.QUEUE_JOIN_UNRANKED); + toReturn[1] = items.get(HotbarItem.QUEUE_JOIN_RANKED); + + if (praxiPlayer.getRematchData() != null) { + if (praxiPlayer.getRematchData().isReceive()) { + toReturn[2] = items.get(HotbarItem.REMATCH_ACCEPT); + } else { + toReturn[2] = items.get(HotbarItem.REMATCH_REQUEST); + } + + toReturn[4] = items.get(HotbarItem.PARTY_CREATE); + + if (Praxi.getInstance().getEventManager().getActiveEvent() != null && Praxi.getInstance().getEventManager().getActiveEvent().isWaiting()) { + toReturn[6] = items.get(HotbarItem.EVENT_JOIN); + } + } else { + if (Praxi.getInstance().getEventManager().getActiveEvent() != null && Praxi.getInstance().getEventManager().getActiveEvent().isWaiting()) { + toReturn[3] = items.get(HotbarItem.EVENT_JOIN); + toReturn[5] = items.get(HotbarItem.PARTY_CREATE); + } else { + toReturn[4] = items.get(HotbarItem.PARTY_CREATE); + } + } + + toReturn[7] = items.get(HotbarItem.SETTINGS); + toReturn[8] = items.get(HotbarItem.KIT_EDITOR); + } else { + if (praxiPlayer.getParty().isLeader(praxiPlayer.getUuid())) { + toReturn[0] = items.get(HotbarItem.PARTY_EVENTS); + toReturn[2] = items.get(HotbarItem.PARTY_INFORMATION); + toReturn[3] = items.get(HotbarItem.OTHER_PARTIES); + toReturn[5] = items.get(HotbarItem.PARTY_DISBAND); + toReturn[7] = items.get(HotbarItem.SETTINGS); + toReturn[8] = items.get(HotbarItem.KIT_EDITOR); + } else { + toReturn[0] = items.get(HotbarItem.PARTY_INFORMATION); + toReturn[2] = items.get(HotbarItem.OTHER_PARTIES); + toReturn[4] = items.get(HotbarItem.PARTY_LEAVE); + toReturn[7] = items.get(HotbarItem.SETTINGS); + toReturn[8] = items.get(HotbarItem.KIT_EDITOR); + } + } + } + break; + case QUEUE: { + toReturn[0] = items.get(HotbarItem.QUEUE_LEAVE); + toReturn[7] = items.get(HotbarItem.SETTINGS); + toReturn[8] = items.get(HotbarItem.KIT_EDITOR); + } + break; + case EVENT_SPECTATE: { + toReturn[0] = items.get(HotbarItem.EVENT_LEAVE); + toReturn[8] = items.get(HotbarItem.SETTINGS); + } + break; + case MATCH_SPECTATE: { + toReturn[0] = items.get(HotbarItem.SPECTATE_STOP); + + if (!praxiPlayer.getMatch().isRanked()) { + toReturn[5] = items.get(HotbarItem.VIEW_INVENTORY); + } + + toReturn[8] = items.get(HotbarItem.SETTINGS); + } + break; + } + + return toReturn; + } + + public static HotbarItem fromItemStack(ItemStack itemStack) { + for (Map.Entry entry : PlayerHotbar.getItems().entrySet()) { + if (entry.getValue() != null && entry.getValue().equals(itemStack)) { + return entry.getKey(); + } + } + + return null; + } + + @AllArgsConstructor + public enum HotbarItem { + QUEUE_JOIN_RANKED, + QUEUE_JOIN_UNRANKED, + QUEUE_LEAVE, + PARTY_EVENTS, + PARTY_CREATE, + PARTY_DISBAND, + PARTY_LEAVE, + PARTY_INFORMATION, + OTHER_PARTIES, + SETTINGS, + KIT_EDITOR, + SPECTATE_STOP, + VIEW_INVENTORY, + EVENT_JOIN, + EVENT_LEAVE, + REMATCH_REQUEST, + REMATCH_ACCEPT + } + + public enum HotbarLayout { + LOBBY, + QUEUE, + MATCH_SPECTATE, + EVENT_SPECTATE, + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/PlayerState.java b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerState.java new file mode 100644 index 0000000..0454e47 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerState.java @@ -0,0 +1,11 @@ +package me.joeleoli.praxi.player; + +public enum PlayerState { + + IN_LOBBY, + IN_QUEUE, + IN_MATCH, + IN_EVENT, + SPECTATE_MATCH + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/PlayerStatistics.java b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerStatistics.java new file mode 100644 index 0000000..07a1a1f --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/PlayerStatistics.java @@ -0,0 +1,68 @@ +package me.joeleoli.praxi.player; + +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; +import me.joeleoli.praxi.ladder.Ladder; + +@Getter +public class PlayerStatistics { + + private Map ladders; + + public PlayerStatistics() { + this.ladders = new HashMap<>(); + + for (Ladder ladder : Ladder.getLadders()) { + this.ladders.put(ladder.getName(), new LadderStatistics()); + } + } + + public int getElo(Ladder ladder) { + if (!this.ladders.containsKey(ladder.getName())) { + return 1000; + } + + return this.ladders.get(ladder.getName()).getElo(); + } + + public LadderStatistics getLadderStatistics(Ladder ladder) { + LadderStatistics ladderStatistics = this.ladders.get(ladder.getName()); + + if (ladderStatistics == null) { + ladderStatistics = new LadderStatistics(); + this.ladders.put(ladder.getName(), ladderStatistics); + } + + return ladderStatistics; + } + + public int getWins() { + int wins = 0; + + for (LadderStatistics stats : this.ladders.values()) { + wins += stats.getWon(); + } + + return wins; + } + + public double getWinRatio() { + int wins = 0; + int losses = 0; + + for (LadderStatistics ladder : this.ladders.values()) { + wins += ladder.getWon(); + losses += ladder.getLost(); + } + + if (losses == 0) { + return 100.0; + } else if (wins == 0 && losses > 0) { + return 0.0; + } else { + return (wins / (wins + losses)) * 100; + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/PracticeSetting.java b/plugin/src/main/java/me/joeleoli/praxi/player/PracticeSetting.java new file mode 100644 index 0000000..fd60c06 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/PracticeSetting.java @@ -0,0 +1,12 @@ +package me.joeleoli.praxi.player; + +import me.joeleoli.nucleus.player.DefinedSetting; + +public enum PracticeSetting implements DefinedSetting { + + RECEIVE_DUEL_REQUESTS, + SHOW_SCOREBOARD, + ALLOW_SPECTATORS, + PING_FACTOR, + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/PraxiPlayer.java b/plugin/src/main/java/me/joeleoli/praxi/player/PraxiPlayer.java new file mode 100644 index 0000000..443994a --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/PraxiPlayer.java @@ -0,0 +1,394 @@ +package me.joeleoli.praxi.player; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import me.joeleoli.nucleus.cooldown.Cooldown; +import me.joeleoli.nucleus.player.PlayerInfo; +import me.joeleoli.nucleus.util.InventoryUtil; +import me.joeleoli.nucleus.util.PlayerUtil; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.duel.DuelProcedure; +import me.joeleoli.praxi.duel.DuelRequest; +import me.joeleoli.praxi.events.Event; +import me.joeleoli.praxi.kit.Kit; +import me.joeleoli.praxi.kit.NamedKit; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.party.Party; +import me.joeleoli.praxi.queue.QueuePlayer; +import org.bson.Document; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +@Getter +public class PraxiPlayer extends PlayerInfo { + + @Getter + private static Map players = new HashMap<>(); + + @Setter + private PlayerState state; + private PlayerStatistics statistics = new PlayerStatistics(); + private KitEditor kitEditor = new KitEditor(); + private Map kits = new HashMap<>(); + @Setter + private Party party; + @Setter + private Match match; + @Setter + private Event event; + @Setter + private QueuePlayer queuePlayer; + @Setter + private Cooldown enderpearlCooldown = new Cooldown(0); + private Map sentDuelRequests = new HashMap<>(); + @Setter + private DuelProcedure duelProcedure; + @Setter + private RematchData rematchData; + private boolean loaded; + + public PraxiPlayer(UUID uuid, String name) { + super(uuid, name); + + this.state = PlayerState.IN_LOBBY; + + for (Ladder ladder : Ladder.getLadders()) { + this.kits.put(ladder.getName(), new NamedKit[4]); + } + } + + public static PraxiPlayer getByUuid(UUID uuid) { + PraxiPlayer praxiPlayer = players.get(uuid); + + if (praxiPlayer == null) { + praxiPlayer = new PraxiPlayer(uuid, null); + } + + return praxiPlayer; + } + + public boolean canSendDuelRequest(Player player) { + if (!this.sentDuelRequests.containsKey(player.getUniqueId())) { + return true; + } + + final DuelRequest request = this.sentDuelRequests.get(player.getUniqueId()); + + if (request.isExpired()) { + this.sentDuelRequests.remove(player.getUniqueId()); + return true; + } else { + return false; + } + } + + public boolean isPendingDuelRequest(Player player) { + if (!this.sentDuelRequests.containsKey(player.getUniqueId())) { + return false; + } + + final DuelRequest request = this.sentDuelRequests.get(player.getUniqueId()); + + if (request.isExpired()) { + this.sentDuelRequests.remove(player.getUniqueId()); + return false; + } else { + return true; + } + } + + public boolean isInLobby() { + return this.state == PlayerState.IN_LOBBY; + } + + public boolean isInQueue() { + return this.state == PlayerState.IN_QUEUE && this.queuePlayer != null; + } + + public boolean isInMatch() { + return (this.state == PlayerState.IN_MATCH) && this.match != null; + } + + public boolean isSpectating() { + return this.state == PlayerState.SPECTATE_MATCH && this.match != null; + } + + public boolean isInEvent() { + return this.state == PlayerState.IN_EVENT && this.event != null; + } + + public boolean isBusy() { + return this.isInMatch() || this.isInQueue() || this.isInEvent() || this.isSpectating() || + this.getParty() != null; + } + + public void refreshHotbar() { + final Player player = this.toPlayer(); + + if (player == null) { + return; + } + + if (this.isInLobby() && !this.kitEditor.isActive()) { + boolean update = false; + + if (this.rematchData != null) { + final Player target = Bukkit.getPlayer(this.rematchData.getTarget()); + + if (System.currentTimeMillis() - this.rematchData.getTimestamp() >= 30_000) { + this.rematchData = null; + update = true; + } else if (target == null || !target.isOnline()) { + this.rematchData = null; + update = true; + } else { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (!(praxiPlayer.isInLobby() || praxiPlayer.isInQueue())) { + this.rematchData = null; + update = true; + } else if (praxiPlayer.getRematchData() == null) { + this.rematchData = null; + update = true; + } else if (!this.rematchData.getKey().equals(praxiPlayer.getRematchData().getKey())) { + this.rematchData = null; + update = true; + } else if (this.rematchData.isReceive()) { + final int requestSlot = player.getInventory().first(PlayerHotbar.getItems() + .get(PlayerHotbar.HotbarItem.REMATCH_REQUEST)); + + if (requestSlot != -1) { + update = true; + } + } + } + } + + { + final Event activeEvent = Praxi.getInstance().getEventManager().getActiveEvent(); + final int eventSlot = + player.getInventory().first(PlayerHotbar.getItems().get(PlayerHotbar.HotbarItem.EVENT_JOIN)); + + if (eventSlot == -1 && activeEvent != null && activeEvent.isWaiting()) { + update = true; + } else if (eventSlot != -1 && (activeEvent == null || !activeEvent.isWaiting())) { + update = true; + } + } + + if (update) { + TaskUtil.run(this::loadHotbar); + } + } + } + + public void loadHotbar() { + Player player = this.toPlayer(); + + if (player == null || !player.isOnline()) { + return; + } + + PlayerUtil.reset(player, false); + + if (this.isInLobby()) { + player.getInventory().setContents(PlayerHotbar.getLayout(PlayerHotbar.HotbarLayout.LOBBY, this)); + } else if (this.isInQueue()) { + player.getInventory().setContents(PlayerHotbar.getLayout(PlayerHotbar.HotbarLayout.QUEUE, this)); + } else if (this.isSpectating()) { + player.getInventory().setContents(PlayerHotbar.getLayout(PlayerHotbar.HotbarLayout.MATCH_SPECTATE, this)); + } else if (this.isInEvent()) { + player.getInventory().setContents(PlayerHotbar.getLayout(PlayerHotbar.HotbarLayout.EVENT_SPECTATE, this)); + } + + player.updateInventory(); + } + + public NamedKit[] getKits(Ladder ladder) { + return this.kits.get(ladder.getName()); + } + + public NamedKit getKit(Ladder ladder, int index) { + return this.kits.get(ladder.getName())[index]; + } + + public void replaceKit(Ladder ladder, int index, NamedKit kit) { + NamedKit[] kits = this.kits.get(ladder.getName()); + kits[index] = kit; + + this.kits.put(ladder.getName(), kits); + } + + public void deleteKit(Ladder ladder, NamedKit kit) { + if (kit == null) { + return; + } + + NamedKit[] kits = this.kits.get(ladder.getName()); + + for (int i = 0; i < 4; i++) { + if (kits[i] != null && kits[i].equals(kit)) { + kits[i] = null; + break; + } + } + + this.kits.put(ladder.getName(), kits); + } + + public List getKitItems(Ladder ladder) { + List toReturn = new ArrayList<>(); + + toReturn.add(Kit.DEFAULT_KIT); + + for (NamedKit kit : this.kits.get(ladder.getName())) { + if (kit != null) { + final ItemStack itemStack = new ItemStack(Material.ENCHANTED_BOOK); + final ItemMeta itemMeta = itemStack.getItemMeta(); + + itemMeta.setDisplayName(ChatColor.GOLD + "Kit: " + ChatColor.YELLOW + kit.getName()); + itemMeta.setLore(Arrays.asList( + ChatColor.GRAY + "Right-click with this book in your", + ChatColor.GRAY + "hand to receive this kit." + )); + itemStack.setItemMeta(itemMeta); + + toReturn.add(itemStack); + } + } + + return toReturn; + } + + public void load() { + try { + Document document = Praxi.getInstance().getPraxiMongo().getPlayer(this.getUuid()); + + if (document == null) { + this.loaded = true; + this.save(); + return; + } + + if (this.getName() == null) { + this.setName(document.getString("name")); + } + + final Document statisticsDocument = (Document) document.get("statistics"); + final Document laddersDocument = (Document) statisticsDocument.get("ladders"); + final Document kitsDocument = (Document) document.get("kits"); + + for (String key : laddersDocument.keySet()) { + final Document ladderDocument = (Document) laddersDocument.get(key); + final Ladder ladder = Ladder.getByName(key); + + if (ladder == null) { + continue; + } + + LadderStatistics ladderStatistics = new LadderStatistics(); + + ladderStatistics.setElo(ladderDocument.getInteger("elo")); + ladderStatistics.setWon(ladderDocument.getInteger("won")); + ladderStatistics.setLost(ladderDocument.getInteger("lost")); + + this.statistics.getLadders().put(ladder.getName(), ladderStatistics); + } + + for (String key : kitsDocument.keySet()) { + Ladder ladder = Ladder.getByName(key); + + if (ladder == null) { + continue; + } + + JsonArray kitsArray = Praxi.PARSER.parse(kitsDocument.getString(key)).getAsJsonArray(); + NamedKit[] kits = new NamedKit[4]; + + for (JsonElement kitElement : kitsArray) { + JsonObject kitObject = kitElement.getAsJsonObject(); + + NamedKit kit = new NamedKit(kitObject.get("name").getAsString()); + + kit.setArmor(InventoryUtil.deserializeInventory(kitObject.get("armor").getAsString())); + kit.setContents(InventoryUtil.deserializeInventory(kitObject.get("contents").getAsString())); + + kits[kitObject.get("index").getAsInt()] = kit; + } + + this.kits.put(ladder.getName(), kits); + } + } catch (Exception e) { + e.printStackTrace(); + return; + } + + this.loaded = true; + } + + public void save() { + Document laddersDocument = new Document(); + + for (Map.Entry entry : this.statistics.getLadders().entrySet()) { + Document ladder = new Document(); + + ladder.put("elo", entry.getValue().getElo()); + ladder.put("won", entry.getValue().getWon()); + ladder.put("lost", entry.getValue().getLost()); + + laddersDocument.put(entry.getKey(), ladder); + } + + Document statisticsDocument = new Document(); + + statisticsDocument.put("ladders", laddersDocument); + + Document kitsDocument = new Document(); + + for (Map.Entry entry : this.kits.entrySet()) { + JsonArray kitsArray = new JsonArray(); + + for (int i = 0; i < 4; i++) { + NamedKit kit = entry.getValue()[i]; + + if (kit != null) { + JsonObject kitObject = new JsonObject(); + + kitObject.addProperty("index", i); + kitObject.addProperty("name", kit.getName()); + kitObject.addProperty("armor", InventoryUtil.serializeInventory(kit.getArmor())); + kitObject.addProperty("contents", InventoryUtil.serializeInventory(kit.getContents())); + + kitsArray.add(kitObject); + } + } + + kitsDocument.put(entry.getKey(), kitsArray.toString()); + } + + Document document = new Document(); + + document.put("uuid", this.getUuid().toString()); + document.put("name", this.getName()); + document.put("statistics", statisticsDocument); + document.put("kits", kitsDocument); + + Praxi.getInstance().getPraxiMongo().replacePlayer(this, document); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/RematchData.java b/plugin/src/main/java/me/joeleoli/praxi/player/RematchData.java new file mode 100644 index 0000000..9232da6 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/RematchData.java @@ -0,0 +1,127 @@ +package me.joeleoli.praxi.player; + +import java.util.UUID; +import lombok.Getter; +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.chat.ChatComponentBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.impl.SoloMatch; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import org.bukkit.entity.Player; + +@Getter +public class RematchData { + + private static final HoverEvent HOVER_EVENT = new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new ChatComponentBuilder(Style.YELLOW + "Click to accept this rematch invite.").create() + ); + private static final ClickEvent CLICK_EVENT = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/rematch"); + + private UUID key; + private UUID sender; + private UUID target; + private Ladder ladder; + private Arena arena; + private boolean sent; + private boolean receive; + private long timestamp = System.currentTimeMillis(); + + public RematchData(UUID key, UUID sender, UUID target, Ladder ladder, Arena arena) { + this.key = key; + this.sender = sender; + this.target = target; + this.ladder = ladder; + this.arena = arena; + } + + public void request() { + + final Player sender = Praxi.getInstance().getServer().getPlayer(this.sender); + final Player target = Praxi.getInstance().getServer().getPlayer(this.target); + + if (sender == null || target == null) { + return; + } + + final PraxiPlayer senderPraxiPlayer = PraxiPlayer.getByUuid(sender.getUniqueId()); + final PraxiPlayer targetPraxiPlayer = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (senderPraxiPlayer.getRematchData() == null || targetPraxiPlayer.getRematchData() == null || + !senderPraxiPlayer.getRematchData().getKey().equals(targetPraxiPlayer.getRematchData().getKey())) { + return; + } + + if (senderPraxiPlayer.isBusy()) { + sender.sendMessage(Style.RED + "You cannot duel right now."); + return; + } + + sender.sendMessage(Style.translate( + "&eYou sent a rematch request to &d" + target.getName() + " &eon arena &d" + this.arena.getName() + + "&e.")); + target.sendMessage(Style.translate( + "&d" + sender.getName() + " &ehas sent you a rematch request on arena &d" + this.arena.getName() + + "&e.")); + target.sendMessage(new ChatComponentBuilder("").parse("&6Click here or type &b/rematch &6to accept the invite.") + .attachToEachPart(HOVER_EVENT).attachToEachPart(CLICK_EVENT) + .create()); + + this.sent = true; + targetPraxiPlayer.getRematchData().receive = true; + + senderPraxiPlayer.refreshHotbar(); + targetPraxiPlayer.refreshHotbar(); + } + + public void accept() { + final Player sender = Praxi.getInstance().getServer().getPlayer(this.sender); + final Player target = Praxi.getInstance().getServer().getPlayer(this.target); + + if (sender == null || target == null || !sender.isOnline() || !target.isOnline()) { + return; + } + + final PraxiPlayer senderPraxiPlayer = PraxiPlayer.getByUuid(sender.getUniqueId()); + final PraxiPlayer targetPraxiPlayer = PraxiPlayer.getByUuid(target.getUniqueId()); + + if (senderPraxiPlayer.getRematchData() == null || targetPraxiPlayer.getRematchData() == null || + !senderPraxiPlayer.getRematchData().getKey().equals(targetPraxiPlayer.getRematchData().getKey())) { + return; + } + + if (senderPraxiPlayer.isBusy()) { + sender.sendMessage(Style.RED + "You cannot duel right now."); + return; + } + + if (targetPraxiPlayer.isBusy()) { + sender.sendMessage(NucleusAPI.getColoredName(target) + Style.RED + " is currently busy."); + return; + } + + Arena arena = this.arena; + + if (arena.isActive()) { + arena = Arena.getRandom(this.ladder); + } + + if (arena == null) { + sender.sendMessage(Style.RED + "Tried to start a match but there are no available arenas."); + return; + } + + arena.setActive(true); + + Match match = new SoloMatch(new MatchPlayer(sender), new MatchPlayer(target), this.ladder, arena, false, true); + + match.handleStart(); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/player/gui/PlayerSettingsMenu.java b/plugin/src/main/java/me/joeleoli/praxi/player/gui/PlayerSettingsMenu.java new file mode 100644 index 0000000..ed79786 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/player/gui/PlayerSettingsMenu.java @@ -0,0 +1,151 @@ +package me.joeleoli.praxi.player.gui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.player.DefinedSetting; +import me.joeleoli.nucleus.player.NucleusPlayer; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TextSplitter; +import me.joeleoli.praxi.player.PracticeSetting; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +public class PlayerSettingsMenu extends Menu { + + @Override + public String getTitle(Player player) { + return Style.PINK + Style.BOLD + "Your Settings"; + } + + @Override + public Map getButtons(Player player) { + final Map buttons = new HashMap<>(); + + for (SettingInfo settingInfo : SettingInfo.values()) { + buttons.put(buttons.size(), new ToggleButton(settingInfo)); + } + + return buttons; + } + + @AllArgsConstructor + private enum SettingInfo { + SCOREBOARD( + PracticeSetting.SHOW_SCOREBOARD, + Style.PINK + Style.BOLD + "Scoreboard", + "If enabled, information will be displayed on your side scoreboard.", + Material.ITEM_FRAME, + "Show your scoreboard", + "Hide your scoreboard" + ), + SPECTATORS( + PracticeSetting.ALLOW_SPECTATORS, + Style.AQUA + Style.BOLD + "Spectators", + "If enabled, players can spectate your match with /spectate.", + Material.REDSTONE_TORCH_ON, + "Let players spectate your matches", + "Don't let players spectate your matches" + ), + DUEL_REQUESTS( + PracticeSetting.RECEIVE_DUEL_REQUESTS, + Style.RED + Style.BOLD + "Duel Requests", + "If enabled, players can request you duel requests.", + Material.BLAZE_ROD, + "Let players request you duel requests", + "Don't let players request you duel requests" + ), + GLOBAL_MESSAGES( + DefinedSetting.GlobalPlayerSetting.RECEIVE_GLOBAL_MESSAGES, + Style.GREEN + Style.BOLD + "Global Messages", + "If enabled, you will receive global chat messages.", + Material.BOOK_AND_QUILL, + "Receive global chat messages", + "Don't receive global chat message" + ), + PRIVATE_MESSAGES( + DefinedSetting.GlobalPlayerSetting.RECEIVE_PRIVATE_MESSAGES, + Style.BLUE + Style.BOLD + "Private Messages", + "If enabled, you will receive private chat messages.", + Material.NAME_TAG, + "Receive private chat messages", + "Don't receive private chat message" + ), + MESSAGE_SOUNDS( + DefinedSetting.GlobalPlayerSetting.PLAY_MESSAGE_SOUNDS, + Style.YELLOW + Style.BOLD + "Message Sounds", + "If enabled, you will be notified via sound when you receive private messages.", + Material.RECORD_7, + "Play message sounds", + "Don't play message sounds" + ), + PING_FACTOR( + PracticeSetting.PING_FACTOR, + Style.DARK_PURPLE + Style.BOLD + "Ping Factor", + "If enabled, you will only be matched against players that have a similar ping to you.", + Material.EYE_OF_ENDER, + "Be matched against players with similar ping", + "Be matched against players with any ping" + ); + + private DefinedSetting setting; + private String title; + private String description; + private Material material; + private String enabledDescription; + private String disabledDescription; + + public void toggle(Player player) { + final NucleusPlayer nucleusPlayer = NucleusPlayer.getByUuid(player.getUniqueId()); + nucleusPlayer.getSettings().getSettings().put(this.setting, !this.get(player)); + } + + public boolean get(Player player) { + final NucleusPlayer nucleusPlayer = NucleusPlayer.getByUuid(player.getUniqueId()); + return nucleusPlayer.getSettings().getBoolean(this.setting); + } + } + + @AllArgsConstructor + private static class ToggleButton extends Button { + + private SettingInfo settingInfo; + + @Override + public ItemStack getButtonItem(Player player) { + final List lore = new ArrayList<>(); + + lore.add(""); + lore.addAll(TextSplitter.split(this.settingInfo.description, Style.YELLOW)); + lore.add(""); + lore.add(" " + (this.settingInfo.get(player) ? Style.GREEN + Style.UNICODE_ARROWS_RIGHT : " ") + " " + + Style.GOLD + this.settingInfo.enabledDescription); + lore.add(" " + (!this.settingInfo.get(player) ? Style.GREEN + Style.UNICODE_ARROWS_RIGHT : " ") + " " + + Style.GOLD + this.settingInfo.disabledDescription); + + return new ItemBuilder(this.settingInfo.material) + .name(this.settingInfo.title) + .lore(lore) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hbSlot) { + this.settingInfo.toggle(player); + } + + @Override + public boolean shouldUpdate(Player player, int slot, ClickType clickType) { + return true; + } + + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/queue/Queue.java b/plugin/src/main/java/me/joeleoli/praxi/queue/Queue.java new file mode 100644 index 0000000..a6002b6 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/queue/Queue.java @@ -0,0 +1,94 @@ +package me.joeleoli.praxi.queue; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import lombok.Getter; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.ladder.Ladder; +import me.joeleoli.praxi.player.PlayerState; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +@Getter +public class Queue { + + @Getter + private static List queues = new ArrayList<>(); + + private UUID uuid = UUID.randomUUID(); + private Ladder ladder; + private boolean ranked; + private LinkedList players; + + public Queue(Ladder ladder, boolean ranked) { + this.ladder = ladder; + this.ranked = ranked; + this.players = new LinkedList<>(); + + queues.add(this); + } + + public static Queue getByUuid(UUID uuid) { + for (Queue queue : queues) { + if (queue.getUuid().equals(uuid)) { + return queue; + } + } + + return null; + } + + public static Queue getByPredicate(Predicate predicate) { + for (Queue queue : queues) { + if (predicate.test(queue)) { + return queue; + } + } + + return null; + } + + public void addPlayer(Player player, int elo) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + final QueuePlayer queuePlayer = new QueuePlayer(this.uuid, player.getUniqueId()); + + if (this.ranked) { + queuePlayer.setElo(elo); + } + + praxiPlayer.setState(PlayerState.IN_QUEUE); + praxiPlayer.setQueuePlayer(queuePlayer); + praxiPlayer.loadHotbar(); + + player.sendMessage( + Style.YELLOW + "You joined the " + Style.PINK + (this.ranked ? "Ranked" : "Unranked") + " " + + this.ladder.getName() + Style.YELLOW + " queue."); + + this.players.add(queuePlayer); + } + + public QueuePlayer removePlayer(QueuePlayer queuePlayer) { + this.players.remove(queuePlayer); + + final Player player = Bukkit.getPlayer(queuePlayer.getPlayerUuid()); + + if (player != null && player.isOnline()) { + player.sendMessage( + Style.YELLOW + "You left the " + Style.PINK + (this.ranked ? "Ranked" : "Unranked") + " " + + this.ladder.getName() + Style.YELLOW + " queue."); + } + + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(queuePlayer.getPlayerUuid()); + + praxiPlayer.setQueuePlayer(null); + praxiPlayer.setState(PlayerState.IN_LOBBY); + praxiPlayer.loadHotbar(); + + return queuePlayer; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/queue/QueuePlayer.java b/plugin/src/main/java/me/joeleoli/praxi/queue/QueuePlayer.java new file mode 100644 index 0000000..a9f0baf --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/queue/QueuePlayer.java @@ -0,0 +1,59 @@ +package me.joeleoli.praxi.queue; + +import java.util.UUID; +import lombok.Data; + +@Data +public class QueuePlayer { + + private UUID queueUuid; + private UUID playerUuid; + private int elo; + private int range = 25; + private long start = System.currentTimeMillis(); + private int ticked; + + public QueuePlayer(UUID queueUuid, UUID playerUuid) { + this.queueUuid = queueUuid; + this.playerUuid = playerUuid; + } + + public void tickRange() { + this.ticked++; + + if (this.ticked >= 20) { + this.range += 25; + this.ticked = 0; + } + } + + public Queue getQueue() { + return Queue.getByUuid(this.queueUuid); + } + + public boolean isInRange(int elo) { + return elo >= (this.elo - this.range) && elo <= (this.elo + this.range); + } + + public long getPassed() { + return System.currentTimeMillis() - this.start; + } + + public int getMinRange() { + int min = this.elo - this.range; + + return min < 0 ? 0 : min; + } + + public int getMaxRange() { + int max = this.elo + this.range; + + return max > 2500 ? 2500 : max; + } + + @Override + public boolean equals(Object o) { + return o instanceof QueuePlayer && ((QueuePlayer) o).getPlayerUuid().equals(this.playerUuid); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/queue/QueueThread.java b/plugin/src/main/java/me/joeleoli/praxi/queue/QueueThread.java new file mode 100644 index 0000000..e293ff1 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/queue/QueueThread.java @@ -0,0 +1,135 @@ +package me.joeleoli.praxi.queue; + +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.nucleus.util.TaskUtil; +import me.joeleoli.praxi.arena.Arena; +import me.joeleoli.praxi.match.Match; +import me.joeleoli.praxi.match.MatchPlayer; +import me.joeleoli.praxi.match.impl.SoloMatch; +import me.joeleoli.praxi.player.PracticeSetting; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class QueueThread extends Thread { + + @Override + public void run() { + while (true) { + try { + for (Queue queue : Queue.getQueues()) { + queue.getPlayers().forEach(QueuePlayer::tickRange); + + if (queue.getPlayers().size() < 2) { + continue; + } + + for (QueuePlayer firstQueuePlayer : queue.getPlayers()) { + final Player firstPlayer = Bukkit.getPlayer(firstQueuePlayer.getPlayerUuid()); + + if (firstPlayer == null) { + continue; + } + + final PraxiPlayer firstPraxiPlayer = PraxiPlayer.getByUuid(firstQueuePlayer.getPlayerUuid()); + + for (QueuePlayer secondQueuePlayer : queue.getPlayers()) { + if (firstQueuePlayer.equals(secondQueuePlayer)) { + continue; + } + + final Player secondPlayer = Bukkit.getPlayer(secondQueuePlayer.getPlayerUuid()); + final PraxiPlayer secondPraxiPlayer = + PraxiPlayer.getByUuid(secondQueuePlayer.getPlayerUuid()); + + if (secondPlayer == null) { + continue; + } + + if (NucleusAPI.getSetting(firstPlayer, PracticeSetting.PING_FACTOR) || + NucleusAPI.getSetting(secondPlayer, PracticeSetting.PING_FACTOR)) { + if (firstPlayer.getPing() >= secondPlayer.getPing()) { + if (firstPlayer.getPing() - secondPlayer.getPing() >= 50) { + continue; + } + } else { + if (secondPlayer.getPing() - firstPlayer.getPing() >= 50) { + continue; + } + } + } + + if (queue.isRanked()) { + if (!firstQueuePlayer.isInRange(secondQueuePlayer.getElo()) || + !secondQueuePlayer.isInRange(firstQueuePlayer.getElo())) { + continue; + } + } + + // Find arena + final Arena arena = Arena.getRandom(queue.getLadder()); + + if (arena == null) { + continue; + } + + // Update arena + arena.setActive(true); + + // Remove players from queue + queue.getPlayers().remove(firstQueuePlayer); + queue.getPlayers().remove(secondQueuePlayer); + + final MatchPlayer firstMatchPlayer = new MatchPlayer(firstPlayer); + final MatchPlayer secondMatchPlayer = new MatchPlayer(secondPlayer); + + if (queue.isRanked()) { + firstMatchPlayer.setElo(firstPraxiPlayer.getStatistics().getElo(queue.getLadder())); + secondMatchPlayer.setElo(secondPraxiPlayer.getStatistics().getElo(queue.getLadder())); + } + + // Create match + final Match match = new SoloMatch(queue.getUuid(), firstMatchPlayer, secondMatchPlayer, + queue.getLadder(), arena, queue.isRanked(), false + ); + + final String[] opponentMessages = + this.formatOpponentMessages(firstPlayer.getName(), secondPlayer.getName(), + firstMatchPlayer.getElo(), secondMatchPlayer.getElo(), queue.isRanked() + ); + + firstPlayer.sendMessage(opponentMessages[0]); + secondPlayer.sendMessage(opponentMessages[1]); + + TaskUtil.run(match::handleStart); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + try { + Thread.sleep(200L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private String[] formatOpponentMessages(String player1, String player2, int player1Elo, int player2Elo, + boolean ranked) { + final String player1Format = player1 + (ranked ? Style.GRAY + " (" + player1Elo + ")" : ""); + final String player2Format = player2 + (ranked ? Style.GRAY + " (" + player2Elo + ")" : ""); + + return new String[]{ + Style.YELLOW + Style.BOLD + "Found opponent: " + Style.GREEN + player1Format + Style.PINK + " vs. " + + Style.RED + player2Format, + Style.YELLOW + Style.BOLD + "Found opponent: " + Style.GREEN + player2Format + Style.PINK + " vs. " + + Style.RED + player1Format + }; + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/queue/gui/QueueJoinMenu.java b/plugin/src/main/java/me/joeleoli/praxi/queue/gui/QueueJoinMenu.java new file mode 100644 index 0000000..84bce15 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/queue/gui/QueueJoinMenu.java @@ -0,0 +1,92 @@ +package me.joeleoli.praxi.queue.gui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import me.joeleoli.nucleus.NucleusAPI; +import me.joeleoli.nucleus.menu.Button; +import me.joeleoli.nucleus.menu.Menu; +import me.joeleoli.nucleus.util.ItemBuilder; +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.player.PraxiPlayer; +import me.joeleoli.praxi.queue.Queue; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +@AllArgsConstructor +public class QueueJoinMenu extends Menu { + + private boolean ranked; + + @Override + public String getTitle(Player player) { + return Style.GOLD + "Join " + (this.ranked ? "Ranked" : "Unranked") + " Queue"; + } + + @Override + public Map getButtons(Player player) { + Map buttons = new HashMap<>(); + + int i = 0; + + for (Queue queue : Queue.getQueues()) { + if (queue.isRanked() == this.ranked) { + buttons.put(i++, new SelectLadderButton(queue)); + } + } + + return buttons; + } + + @AllArgsConstructor + private class SelectLadderButton extends Button { + + private Queue queue; + + @Override + public ItemStack getButtonItem(Player player) { + final List lore = new ArrayList<>(); + + lore.add(Style.YELLOW + "Fighting: " + Style.RESET + Praxi.getInstance().getFightingCount(this.queue)); + lore.add(Style.YELLOW + "Queueing: " + Style.RESET + this.queue.getPlayers().size()); + lore.add(""); + lore.add(Style.YELLOW + "Click here to select " + Style.PINK + Style.BOLD + + this.queue.getLadder().getName() + Style.YELLOW + "."); + + return new ItemBuilder(this.queue.getLadder().getDisplayIcon()) + .name(Style.PINK + Style.BOLD + this.queue.getLadder().getName()).lore(lore) + .build(); + } + + @Override + public void clicked(Player player, int slot, ClickType clickType, int hotbarButton) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if (praxiPlayer == null) { + return; + } + + if (NucleusAPI.isFrozen(player)) { + player.sendMessage(Style.RED + "You cannot queue while frozen."); + return; + } + + if (praxiPlayer.isBusy()) { + player.sendMessage(Style.RED + "You cannot queue right now."); + return; + } + + player.closeInventory(); + + this.queue.addPlayer( + player, + !this.queue.isRanked() ? 0 : praxiPlayer.getStatistics().getElo(this.queue.getLadder()) + ); + } + + } +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/task/ExpBarCooldownTask.java b/plugin/src/main/java/me/joeleoli/praxi/task/ExpBarCooldownTask.java new file mode 100644 index 0000000..18109bc --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/task/ExpBarCooldownTask.java @@ -0,0 +1,37 @@ +package me.joeleoli.praxi.task; + +import me.joeleoli.nucleus.util.Style; +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; + +public class ExpBarCooldownTask implements Runnable { + + @Override + public void run() { + for (Player player : Praxi.getInstance().getServer().getOnlinePlayers()) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + if ((praxiPlayer.isInMatch() || praxiPlayer.isInEvent()) && !praxiPlayer.getEnderpearlCooldown().hasExpired()) { + int seconds = Math.round(praxiPlayer.getEnderpearlCooldown().getRemaining()) / 1_000; + + player.setLevel(seconds); + player.setExp(praxiPlayer.getEnderpearlCooldown().getRemaining() / 16_000.0F); + } else { + if (!praxiPlayer.getEnderpearlCooldown().isNotified()) { + player.sendMessage(Style.PINK + "Your pearl cooldown has expired."); + praxiPlayer.getEnderpearlCooldown().setNotified(true); + } + + if (player.getLevel() > 0) { + player.setLevel(0); + } + + if (player.getExp() > 0.0F) { + player.setExp(0.0F); + } + } + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/task/InventoryCleanupTask.java b/plugin/src/main/java/me/joeleoli/praxi/task/InventoryCleanupTask.java new file mode 100644 index 0000000..08a6383 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/task/InventoryCleanupTask.java @@ -0,0 +1,13 @@ +package me.joeleoli.praxi.task; + +import me.joeleoli.praxi.match.MatchSnapshot; + +public class InventoryCleanupTask implements Runnable { + + @Override + public void run() { + MatchSnapshot.getCache().entrySet() + .removeIf(entry -> System.currentTimeMillis() - entry.getValue().getCreated() >= 45_000); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/task/InviteCleanupTask.java b/plugin/src/main/java/me/joeleoli/praxi/task/InviteCleanupTask.java new file mode 100644 index 0000000..1feb5bd --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/task/InviteCleanupTask.java @@ -0,0 +1,13 @@ +package me.joeleoli.praxi.task; + +import me.joeleoli.praxi.party.Party; + +public class InviteCleanupTask implements Runnable { + + @Override + public void run() { + Party.getParties().forEach(party -> party.getInvited().entrySet().removeIf( + entry -> System.currentTimeMillis() >= entry.getValue() + 30_000)); + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/task/RematchExpireRunnable.java b/plugin/src/main/java/me/joeleoli/praxi/task/RematchExpireRunnable.java new file mode 100644 index 0000000..93bb327 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/task/RematchExpireRunnable.java @@ -0,0 +1,18 @@ +package me.joeleoli.praxi.task; + +import me.joeleoli.praxi.Praxi; +import me.joeleoli.praxi.player.PraxiPlayer; +import org.bukkit.entity.Player; + +public class RematchExpireRunnable implements Runnable { + + @Override + public void run() { + for (Player player : Praxi.getInstance().getServer().getOnlinePlayers()) { + final PraxiPlayer praxiPlayer = PraxiPlayer.getByUuid(player.getUniqueId()); + + praxiPlayer.refreshHotbar(); + } + } + +} diff --git a/plugin/src/main/java/me/joeleoli/praxi/task/SaveDataTask.java b/plugin/src/main/java/me/joeleoli/praxi/task/SaveDataTask.java new file mode 100644 index 0000000..cbf48c9 --- /dev/null +++ b/plugin/src/main/java/me/joeleoli/praxi/task/SaveDataTask.java @@ -0,0 +1,14 @@ +package me.joeleoli.praxi.task; + +import me.joeleoli.praxi.player.PraxiPlayer; + +public class SaveDataTask implements Runnable { + + @Override + public void run() { + for (PraxiPlayer praxiPlayer : PraxiPlayer.getPlayers().values()) { + praxiPlayer.save(); + } + } + +} diff --git a/plugin/src/main/resources/arenas.yml b/plugin/src/main/resources/arenas.yml new file mode 100644 index 0000000..e69de29 diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml new file mode 100644 index 0000000..8a08ca2 --- /dev/null +++ b/plugin/src/main/resources/config.yml @@ -0,0 +1,9 @@ +mongo: + host: "127.0.0.1" + port: 27017 + database: "praxi" + authentication: + enabled: false + username: "admin" + password: "dev" + database: "admin" \ No newline at end of file diff --git a/plugin/src/main/resources/ladders.yml b/plugin/src/main/resources/ladders.yml new file mode 100644 index 0000000..cb2fb79 --- /dev/null +++ b/plugin/src/main/resources/ladders.yml @@ -0,0 +1,81 @@ +ladders: + Classic: + enabled: true + build: false + sumo: false + parkour: false + regeneration: true + display-name: "&eClassic" + display-icon: + material: IRON_SWORD + durability: 0 + kit-editor: + allow-potion-fill: true + items: + 1: + material: COOKED_BEEF + amount: 64 + 2: + material: GOLDEN_CARROT + amount: 64 + 3: + material: POTION + durability: 16388 + 4: + material: POTION + durability: 16426 + 5: + material: POTION + durability: 16421 + 6: + material: POTION + durability: 8226 + NoDebuff: + enabled: true + build: false + sumo: false + parkour: false + regeneration: true + display-name: "&6NoDebuff" + display-icon: + material: DIAMOND_SWORD + durability: 0 + kit-editor: + allow-potion-fill: true + items: + 1: + material: COOKED_BEEF + durability: 0 + amount: 64 + 2: + material: GOLDEN_CARROT + durability: 0 + amount: 64 + 3: + material: POTION + durability: 16421 + amount: 1 + 4: + material: POTION + durability: 8226 + amount: 1 + BuildUHC: + enabled: true + build: true + sumo: false + parkour: false + regeneration: false + display-name: "&eBuildUHC" + display-icon: + material: LAVA_BUCKET + durability: 0 + Axe: + enabled: true + build: false + sumo: false + parkour: false + regeneration: true + display-name: "&bAxe" + display-icon: + material: IRON_AXE + durability: 0 \ No newline at end of file diff --git a/plugin/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml new file mode 100644 index 0000000..c2ba0a8 --- /dev/null +++ b/plugin/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +main: me.joeleoli.praxi.Praxi +name: Praxi +version: ${project.version} +author: joeleoli +description: ${project.description} +depend: [Nucleus] \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..81b851d --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + me.joeleoli.praxi-optimized + parent + pom + 1.0-SNAPSHOT + + + plugin + + + + + md5-repo + http://repo.md-5.net/content/groups/public/ + + + dmulloy2-repo + http://repo.dmulloy2.net/nexus/repository/public/ + + + + + + me.joeleoli.ragespigot + ragespigot + 1.8.8-R0.1-SNAPSHOT + provided + + + org.projectlombok + lombok + 1.16.16 + provided + + + + + clean install + praxi-${project.name} + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.0.0 + + false + false + + + + package + + shade + + + + + + + + \ No newline at end of file