diff --git a/build.gradle b/build.gradle index ede0ceb..071c7b2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'net.foulest' -version = '1.4.4' +version = '1.4.5' description = 'KitPvP' // Set the project's language level diff --git a/src/main/java/net/foulest/kitpvp/KitPvP.java b/src/main/java/net/foulest/kitpvp/KitPvP.java index 3f0266c..7c95f7d 100644 --- a/src/main/java/net/foulest/kitpvp/KitPvP.java +++ b/src/main/java/net/foulest/kitpvp/KitPvP.java @@ -97,9 +97,9 @@ public void onEnable() { // Loads the plugin's listeners. MessageUtil.log(Level.INFO, "Loading Listeners..."); - loadListeners(new DeathListener(), new EventListener(), new FlaskListener(), - new ArcherListener(), new FishermanListener(), new KangarooListener(), - new MageListener(), new NinjaListener(), new PyroListener(), + loadListeners(new DeathListener(), new EventListener(), new FlaskListener(), new ArcherListener(), + new FishermanListener(), new JesterListener(), new KangarooListener(), new MageListener(), + new NinjaListener(), new PyroListener(), new ReaperListener(), new SoldierListener(), new TankListener(), new VampireListener()); // Loads the plugin's commands. @@ -107,12 +107,12 @@ public void onEnable() { loadCommands(new BalanceCmd(), new BountyCmd(), new ClearKitCmd(), new CombatTagCmd(), new EcoCmd(), new KitsCmd(), new PayCmd(), new SetSpawnCmd(), new SpawnCmd(), new StatsCmd(), new KitShopCmd(), new ArmorColorCmd(), new KitEnchanterCmd(), new SoupCmd(), - new PotionsCmd(), new KitPvPCmd()); + new PotionsCmd(), new KitPvPCmd(), new PlaySoundCmd()); // Loads the plugin's kits. MessageUtil.log(Level.INFO, "Loading Kits..."); - loadKits(new Archer(), new Fisherman(), new Kangaroo(), new Knight(), new Mage(), - new Ninja(), new Pyro(), new Tank(), new Vampire()); + loadKits(new Archer(), new Fisherman(), new Jester(), new Kangaroo(), new Knight(), new Mage(), + new Ninja(), new Pyro(), new Reaper(), new Soldier(), new Tank(), new Vampire()); // Loads the spawn. MessageUtil.log(Level.INFO, "Loading Spawn..."); diff --git a/src/main/java/net/foulest/kitpvp/cmds/BountyCmd.java b/src/main/java/net/foulest/kitpvp/cmds/BountyCmd.java index de0ea5f..e028d26 100644 --- a/src/main/java/net/foulest/kitpvp/cmds/BountyCmd.java +++ b/src/main/java/net/foulest/kitpvp/cmds/BountyCmd.java @@ -18,12 +18,8 @@ package net.foulest.kitpvp.cmds; import lombok.Data; -import net.foulest.kitpvp.KitPvP; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; -import net.foulest.kitpvp.kits.Kit; -import net.foulest.kitpvp.kits.KitManager; -import net.foulest.kitpvp.region.Spawn; import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; @@ -31,7 +27,6 @@ import net.foulest.kitpvp.util.command.CommandArgs; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/net/foulest/kitpvp/cmds/ClearKitCmd.java b/src/main/java/net/foulest/kitpvp/cmds/ClearKitCmd.java index 43c6cf9..7ae41ad 100644 --- a/src/main/java/net/foulest/kitpvp/cmds/ClearKitCmd.java +++ b/src/main/java/net/foulest/kitpvp/cmds/ClearKitCmd.java @@ -119,6 +119,7 @@ private static void clearKit(@NotNull PlayerData playerData) { playerData.clearCooldowns(); playerData.setActiveKit(null); + player.setMaxHealth(20); player.setHealth(20); player.getInventory().setHeldItemSlot(0); diff --git a/src/main/java/net/foulest/kitpvp/cmds/PayCmd.java b/src/main/java/net/foulest/kitpvp/cmds/PayCmd.java index 7ef2af1..311f6ee 100644 --- a/src/main/java/net/foulest/kitpvp/cmds/PayCmd.java +++ b/src/main/java/net/foulest/kitpvp/cmds/PayCmd.java @@ -43,26 +43,28 @@ public class PayCmd { @Command(name = "pay", description = "Send coins to another player.", usage = "/pay ", inGameOnly = true, permission = "kitpvp.pay") public static void onCommand(@NotNull CommandArgs args) { - CommandSender sender = args.getSender(); + CommandSender commandSender = args.getSender(); + Player sender = args.getPlayer(); - if (args.length() == 2) { - Player player = args.getPlayer(); - - // Checks if the player is null. - if (player == null) { - MessageUtil.messagePlayer(sender, ConstantUtil.IN_GAME_ONLY); - return; - } + // Checks if the player is null. + if (sender == null) { + MessageUtil.messagePlayer(commandSender, ConstantUtil.IN_GAME_ONLY); + return; + } - Location location = player.getLocation(); - String playerName = player.getName(); + if (args.length() == 2) { + // Sender data + PlayerData senderData = PlayerDataManager.getPlayerData(sender); + Location senderLoc = sender.getLocation(); + String senderName = sender.getName(); + // Target data String desiredTarget = args.getArgs(0); Player target = Bukkit.getPlayer(desiredTarget); // Checks if the target is online. if (target == null) { - MessageUtil.messagePlayer(player, ConstantUtil.PLAYER_NOT_FOUND); + MessageUtil.messagePlayer(sender, ConstantUtil.PLAYER_NOT_FOUND); return; } @@ -70,7 +72,7 @@ public static void onCommand(@NotNull CommandArgs args) { // Checks if the amount is a number. if (!StringUtils.isNumeric(desiredAmount)) { - MessageUtil.messagePlayer(player, "&c'" + desiredAmount + "' is not a valid amount."); + MessageUtil.messagePlayer(sender, "&c'" + desiredAmount + "' is not a valid amount."); return; } @@ -78,36 +80,37 @@ public static void onCommand(@NotNull CommandArgs args) { // Checks if the amount is negative. if (amount < 0) { - MessageUtil.messagePlayer(player, "&cThe amount must be positive."); + MessageUtil.messagePlayer(sender, "&cThe amount must be positive."); return; } // Checks if the sender is the target. - if (sender == target) { - player.playSound(location, Sound.VILLAGER_NO, 1.0F, 1.0F); - MessageUtil.messagePlayer(player, "&cYou can't pay yourself."); + if (commandSender == target) { + sender.playSound(senderLoc, Sound.VILLAGER_NO, 1.0F, 1.0F); + MessageUtil.messagePlayer(sender, "&cYou can't pay yourself."); return; } + // Target data PlayerData targetData = PlayerDataManager.getPlayerData(target); - PlayerData senderData = PlayerDataManager.getPlayerData(player); + int targetCoins = targetData.getCoins(); + String targetName = target.getName(); // Checks if the sender has enough coins. if (senderData.getCoins() - amount <= 0) { - MessageUtil.messagePlayer(sender, ConstantUtil.NOT_ENOUGH_COINS); + MessageUtil.messagePlayer(commandSender, ConstantUtil.NOT_ENOUGH_COINS); return; } - int targetCoins = targetData.getCoins(); - + // Transfers the coins. targetData.setCoins(targetCoins + amount); senderData.removeCoins(amount); - MessageUtil.messagePlayer(player, "&a" + playerName + " sent you " + amount + " coins!"); - MessageUtil.messagePlayer(sender, "&aYou sent " + playerName + " " + amount + " coins!"); - return; + // Sends messages to the sender and target. + MessageUtil.messagePlayer(target, "&a" + senderName + " sent you " + amount + " coins!"); + MessageUtil.messagePlayer(sender, "&aYou sent " + targetName + " " + amount + " coins!"); + } else { + MessageUtil.messagePlayer(commandSender, "&cUsage: /pay "); } - - MessageUtil.messagePlayer(sender, "&cUsage: /pay "); } } diff --git a/src/main/java/net/foulest/kitpvp/cmds/PlaySoundCmd.java b/src/main/java/net/foulest/kitpvp/cmds/PlaySoundCmd.java new file mode 100644 index 0000000..2f51f41 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/cmds/PlaySoundCmd.java @@ -0,0 +1,80 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.cmds; + +import lombok.Data; +import net.foulest.kitpvp.util.ConstantUtil; +import net.foulest.kitpvp.util.MessageUtil; +import net.foulest.kitpvp.util.command.Command; +import net.foulest.kitpvp.util.command.CommandArgs; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +/** + * Command for testing different sounds. + * + * @author Foulest + */ +@Data +public class PlaySoundCmd { + + @Command(name = "playsound", description = "Plays a specified or random sound.", + permission = "kitpvp.playsound", usage = "/playsound [name]", inGameOnly = true) + public static void onCommand(@NotNull CommandArgs args) { + CommandSender sender = args.getSender(); + Player player = args.getPlayer(); + + // Checks if the player is null. + if (player == null) { + MessageUtil.messagePlayer(sender, ConstantUtil.IN_GAME_ONLY); + return; + } + + Location location = player.getLocation(); + + // Prints the usage message. + if (args.length() > 1) { + MessageUtil.messagePlayer(sender, "&cUsage: /playsound [name]"); + return; + } + + // Plays a random sound. + if (args.length() == 0) { + // Randomly get a sound from the list. + Sound[] sounds = Sound.values(); + Sound sound = sounds[(int) (Math.random() * sounds.length)]; + String soundName = sound.name(); + + player.playSound(location, sound, 1.0F, 1.0F); + MessageUtil.messagePlayer(sender, "&aPlayed random sound: &e" + soundName); + return; + } + + // Plays a specified sound. + String soundName = args.getArgs(0).toUpperCase(Locale.ROOT); + Sound sound = Sound.valueOf(soundName); + + player.playSound(location, sound, 1.0F, 1.0F); + MessageUtil.messagePlayer(sender, "&aPlayed specified sound: &e" + soundName); + } +} diff --git a/src/main/java/net/foulest/kitpvp/cmds/SpawnCmd.java b/src/main/java/net/foulest/kitpvp/cmds/SpawnCmd.java index 3be89e8..2646a79 100644 --- a/src/main/java/net/foulest/kitpvp/cmds/SpawnCmd.java +++ b/src/main/java/net/foulest/kitpvp/cmds/SpawnCmd.java @@ -24,7 +24,6 @@ import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.region.Regions; import net.foulest.kitpvp.region.Spawn; -import net.foulest.kitpvp.util.BlockUtil; import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.command.Command; @@ -66,7 +65,7 @@ public static void onCommand(@NotNull CommandArgs args) { } // Checks if the player is on the ground. - if (!BlockUtil.isOnGroundOffset(player, 0.001)) { + if (player.getVelocity().getY() != -0.0784000015258789) { MessageUtil.messagePlayer(args.getPlayer(), ConstantUtil.NOT_ON_GROUND); return; } diff --git a/src/main/java/net/foulest/kitpvp/cooldown/Cooldown.java b/src/main/java/net/foulest/kitpvp/cooldown/Cooldown.java new file mode 100644 index 0000000..0094c43 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/cooldown/Cooldown.java @@ -0,0 +1,20 @@ +package net.foulest.kitpvp.cooldown; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.foulest.kitpvp.kits.Kit; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +@Data +@AllArgsConstructor +@EqualsAndHashCode +public class Cooldown { + + public Player player; + public Kit kit; + public Material itemType; + @EqualsAndHashCode.Exclude + public long duration; +} diff --git a/src/main/java/net/foulest/kitpvp/data/PlayerData.java b/src/main/java/net/foulest/kitpvp/data/PlayerData.java index cdac35f..94875f8 100644 --- a/src/main/java/net/foulest/kitpvp/data/PlayerData.java +++ b/src/main/java/net/foulest/kitpvp/data/PlayerData.java @@ -19,6 +19,7 @@ import lombok.Data; import net.foulest.kitpvp.KitPvP; +import net.foulest.kitpvp.cooldown.Cooldown; import net.foulest.kitpvp.enchants.Enchants; import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.KitManager; @@ -60,6 +61,7 @@ public class PlayerData { private final Set ownedKits = new HashSet<>(); private Kit activeKit; private Kit previousKit = KitManager.getKit("Knight"); + private int changeCount; // Player stats private int coins = Settings.startingCoins; @@ -80,9 +82,11 @@ public class PlayerData { // No-fall data private boolean noFall; + private double lastVelocityY; + private long onGroundTicks; // Cooldowns and timers - private final Map cooldowns = new HashMap<>(); + private final List cooldowns = new ArrayList<>(); private @Nullable BukkitTask abilityCooldownNotifier; private BukkitTask teleportToSpawnTask; @@ -92,6 +96,12 @@ public class PlayerData { // Vampire task private @Nullable BukkitTask lifeStealCooldown; + // Reaper mark + private Player activeReaperMark; + + // Soldier rage + private double soldierRage; + /** * Creates a new player data object. * @@ -104,26 +114,30 @@ public PlayerData(@NotNull UUID uniqueId, Player player) { } /** - * Checks if the player has a cooldown for their active kit. + * Checks if the player has a cooldown for their active kit and item. * * @param sendMessage Whether to send a message to the player. * @return Whether the player has a cooldown. */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean hasCooldown(boolean sendMessage) { - long cooldown = cooldowns.containsKey(activeKit) ? (cooldowns.get(activeKit) - System.currentTimeMillis()) : 0L; + public boolean hasCooldown(Material itemType, boolean sendMessage) { + for (Cooldown cooldown : cooldowns) { + if (cooldown.getPlayer() == player && cooldown.getItemType() == itemType) { + long duration = cooldown.getDuration() - System.currentTimeMillis(); - if (cooldown > 0) { - if (sendMessage) { - BigDecimal cooldownDecimal = BigDecimal.valueOf((double) cooldown / 1000).setScale(1, RoundingMode.HALF_UP); - double cooldownDouble = cooldownDecimal.doubleValue(); + if (duration > 0) { + if (sendMessage) { + BigDecimal cooldownDecimal = BigDecimal.valueOf((double) duration / 1000).setScale(1, RoundingMode.HALF_UP); + double cooldownDouble = cooldownDecimal.doubleValue(); - String cooldownMsg = "&cYou are still on cooldown for %time% seconds."; - cooldownMsg = cooldownMsg.replace("%time%", String.valueOf(cooldownDouble)); + String cooldownMsg = "&cYou are still on cooldown for %time% seconds."; + cooldownMsg = cooldownMsg.replace("%time%", String.valueOf(cooldownDouble)); - MessageUtil.messagePlayer(player, cooldownMsg); + MessageUtil.messagePlayer(player, cooldownMsg); + } + return true; + } } - return true; } return false; } @@ -154,23 +168,55 @@ public void clearCooldowns() { * Sets a cooldown for a specific kit. * * @param kit The kit to set the cooldown for. + * @param itemType The item type to set the cooldown for. * @param cooldownTime The time in seconds for the cooldown. * @param notify Whether to notify the player when the cooldown expires. */ - public void setCooldown(Kit kit, int cooldownTime, boolean notify) { - cooldowns.put(kit, System.currentTimeMillis() + cooldownTime * 1000L); + public void setCooldown(Kit kit, Material itemType, int cooldownTime, boolean notify) { + Cooldown cooldown = new Cooldown(player, kit, itemType, System.currentTimeMillis() + cooldownTime * 1000L); + + // Removes any existing cooldowns for the player, kit, and item type. + for (Cooldown existingCooldown : cooldowns) { + if (existingCooldown.getPlayer() == player + && existingCooldown.getKit() == kit + && existingCooldown.getItemType() == itemType) { + cooldowns.remove(existingCooldown); + break; + } + } + + // Adds the new cooldown. + cooldowns.add(cooldown); if (notify) { abilityCooldownNotifier = new BukkitRunnable() { @Override public void run() { MessageUtil.messagePlayer(player, "&aYour ability cooldown has expired."); - cooldowns.remove(kit); + + // Remove the cooldown that just expired. + cooldowns.remove(cooldown); } }.runTaskLater(KitPvP.instance, cooldownTime * 20L); } } + /** + * Gets a cooldown for a specific kit and item type. + * + * @param kit The kit to get the cooldown for. + * @param itemType The item type to get the cooldown for. + * @return The cooldown. + */ + public @Nullable Cooldown getCooldown(Kit kit, Material itemType) { + for (Cooldown cooldown : cooldowns) { + if (cooldown.getPlayer() == player && cooldown.getKit() == kit && cooldown.getItemType() == itemType) { + return cooldown; + } + } + return null; + } + /** * Loads the player's data from the database. * diff --git a/src/main/java/net/foulest/kitpvp/kits/Kit.java b/src/main/java/net/foulest/kitpvp/kits/Kit.java index c969c96..1f94d01 100644 --- a/src/main/java/net/foulest/kitpvp/kits/Kit.java +++ b/src/main/java/net/foulest/kitpvp/kits/Kit.java @@ -57,6 +57,15 @@ default String getName() { return "Default"; } + /** + * Gets the max health of the kit. + * + * @return The max health of the kit. + */ + default double getMaxHealth() { + return 20; + } + /** * Gets the config path of the kit. *

@@ -82,12 +91,28 @@ default ItemStack getDisplayItem() { String configPath = getConfigPath(); String itemName = Settings.config.getString(configPath + ".display-item"); Material material = Material.getMaterial(itemName); + ItemBuilder item; + // Check if the material is valid. if (material == null) { MessageUtil.log(Level.WARNING, "Invalid display item for " + kitName + ": " + itemName); return new ItemBuilder(Material.BARRIER).hideInfo().getItem(); + } else { + // Checks if the item is a SKULL_ITEM. If so, it grabs the other SKULL_ITEM + // from the getArmor() helmet and sets the display item's texture to that. + if (material == Material.SKULL_ITEM) { + List armor = getArmor(); + + if (!armor.isEmpty() && armor.get(0).getItem().getType() == Material.SKULL_ITEM) { + item = armor.get(0).hideInfo(); + return item.getItem(); + } + } + + // Construct the item with the specified material. + item = new ItemBuilder(material).hideInfo(); } - return new ItemBuilder(material).hideInfo().getItem(); + return item.getItem(); } /** @@ -124,16 +149,17 @@ default List getItems() { for (Map itemConfig : itemsConfigList) { String materialName = (String) itemConfig.get("material"); Material material = Material.getMaterial(materialName); + ItemBuilder item; // Check if the material is valid. if (material == null) { - MessageUtil.log(Level.WARNING, "Invalid material for " + kitName + "'s item: " + material); + MessageUtil.log(Level.WARNING, "Invalid material for " + kitName + "'s item: " + materialName); continue; + } else { + // Construct the item with the specified material. + item = new ItemBuilder(material); } - // Construct the item with the specified material. - ItemBuilder item = new ItemBuilder(material); - // Set the item's name. if (itemConfig.containsKey("name")) { String name = (String) itemConfig.get("name"); @@ -483,6 +509,10 @@ default void apply(Player player) { // Sets the player's kit data. playerData.setActiveKit(this); + // Sets the player's max health. + double maxHealth = getMaxHealth(); + player.setMaxHealth(maxHealth); + // Sets the player's potion effects. List effects = getPotionEffects(); if (effects != null) { @@ -633,5 +663,9 @@ default void apply(Player player) { player.playSound(location, Sound.SLIME_WALK, 1, 1); player.updateInventory(); player.closeInventory(); + + // Update the player's kit change count. + int changeCount = playerData.getChangeCount(); + playerData.setChangeCount(changeCount + 1); } } diff --git a/src/main/java/net/foulest/kitpvp/kits/type/Demoman.java b/src/main/java/net/foulest/kitpvp/kits/type/Demoman.java new file mode 100644 index 0000000..77d5c93 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/kits/type/Demoman.java @@ -0,0 +1,33 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.kits.type; + +import net.foulest.kitpvp.kits.Kit; + +/** + * Represents the Demoman kit. + * + * @author Foulest + */ +public class Demoman implements Kit { + + @Override + public String getName() { + return "Demoman"; + } +} diff --git a/src/main/java/net/foulest/kitpvp/kits/type/Jester.java b/src/main/java/net/foulest/kitpvp/kits/type/Jester.java new file mode 100644 index 0000000..2ef8aec --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/kits/type/Jester.java @@ -0,0 +1,33 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.kits.type; + +import net.foulest.kitpvp.kits.Kit; + +/** + * Represents the Jester kit. + * + * @author Foulest + */ +public class Jester implements Kit { + + @Override + public String getName() { + return "Jester"; + } +} diff --git a/src/main/java/net/foulest/kitpvp/kits/type/Reaper.java b/src/main/java/net/foulest/kitpvp/kits/type/Reaper.java new file mode 100644 index 0000000..cf106c4 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/kits/type/Reaper.java @@ -0,0 +1,33 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.kits.type; + +import net.foulest.kitpvp.kits.Kit; + +/** + * Represents the Reaper kit. + * + * @author Foulest + */ +public class Reaper implements Kit { + + @Override + public String getName() { + return "Reaper"; + } +} diff --git a/src/main/java/net/foulest/kitpvp/kits/type/Soldier.java b/src/main/java/net/foulest/kitpvp/kits/type/Soldier.java new file mode 100644 index 0000000..a956c54 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/kits/type/Soldier.java @@ -0,0 +1,33 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.kits.type; + +import net.foulest.kitpvp.kits.Kit; + +/** + * Represents the Soldier kit. + * + * @author Foulest + */ +public class Soldier implements Kit { + + @Override + public String getName() { + return "Soldier"; + } +} diff --git a/src/main/java/net/foulest/kitpvp/listeners/DeathListener.java b/src/main/java/net/foulest/kitpvp/listeners/DeathListener.java index c31889b..c714ec8 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/DeathListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/DeathListener.java @@ -18,10 +18,13 @@ package net.foulest.kitpvp.listeners; import lombok.Data; +import net.foulest.kitpvp.KitPvP; import net.foulest.kitpvp.combattag.CombatTag; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.listeners.kits.ReaperListener; +import net.foulest.kitpvp.region.Regions; import net.foulest.kitpvp.region.Spawn; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; @@ -85,7 +88,6 @@ static void handleDeath(@NotNull Player receiver, boolean onPlayerQuit) { // Runs specific code if the player is killed by another player. if (CombatTag.getLastAttacker(receiver) != null && CombatTag.getLastAttacker(receiver) != receiver) { - // Damager data Player damager = CombatTag.getLastAttacker(receiver); String damagerName = damager.getName(); @@ -97,7 +99,7 @@ static void handleDeath(@NotNull Player receiver, boolean onPlayerQuit) { int damagerCoins = damagerData.getCoins(); // Adds a Flask to the damager's inventory. - if (Settings.flaskEnabled) { + if (Settings.flaskEnabled && !Regions.isInSafezone(damagerLoc)) { FlaskListener.addFlaskToInventory(damager, Settings.flaskAmount); } @@ -145,6 +147,13 @@ static void handleDeath(@NotNull Player receiver, boolean onPlayerQuit) { receiverData.removeBounty(); } + // Removes Reaper marks. + if (damagerData.getActiveReaperMark() == receiver) { + ReaperListener.removeReaperMark(damagerData, true, true); + } else if (receiverData.getActiveReaperMark() == damager) { + ReaperListener.removeReaperMark(receiverData, true, true); + } + // Prints kill messages to both the damager and receiver. MessageUtil.messagePlayer(receiver, "&eYou were killed by &c" + damagerName + " &eon &6" + String.format("%.01f", damagerHealth) + "\u2764&e."); @@ -154,6 +163,13 @@ static void handleDeath(@NotNull Player receiver, boolean onPlayerQuit) { MessageUtil.messagePlayer(receiver, "&cYou killed yourself."); } + // Resets the player's Soldier data. + receiverData.setSoldierRage(0.0); + receiver.removeMetadata("buffBanner", KitPvP.getInstance()); + + // Removes Reaper marks. + ReaperListener.removeReaperMark(receiverData, false, true); + // Clears cooldowns. receiverData.clearCooldowns(); @@ -183,7 +199,7 @@ static void handleDeath(@NotNull Player receiver, boolean onPlayerQuit) { // Teleport the player to spawn. receiver.getInventory().setHeldItemSlot(0); - receiver.teleport(Spawn.getLocation()); + Spawn.teleport(receiver); receiver.getInventory().setHeldItemSlot(0); }, 1L); } diff --git a/src/main/java/net/foulest/kitpvp/listeners/EventListener.java b/src/main/java/net/foulest/kitpvp/listeners/EventListener.java index dd11735..76981d5 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/EventListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/EventListener.java @@ -26,6 +26,7 @@ import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.KitManager; import net.foulest.kitpvp.kits.type.Knight; +import net.foulest.kitpvp.listeners.kits.ReaperListener; import net.foulest.kitpvp.menus.KitEnchanter; import net.foulest.kitpvp.menus.KitSelector; import net.foulest.kitpvp.menus.KitShop; @@ -57,6 +58,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import java.util.logging.Level; @@ -95,6 +97,7 @@ public static void onPlayerJoin(@NotNull PlayerJoinEvent event) { // The rest of the initialization happens here only after data is loaded TaskUtil.runTaskLater(() -> { + player.setMaxHealth(20); player.setHealth(20); player.setGameMode(GameMode.ADVENTURE); player.getInventory().setHeldItemSlot(0); @@ -127,9 +130,11 @@ public static void onPlayerQuit(@NotNull PlayerQuitEvent event) { DeathListener.handleDeath(player, true); } + // Removes any Reaper Marks associated with the player. + ReaperListener.removeReaperMark(playerData, true, true); + ReaperListener.removeReaperMark(playerData, false, true); + // Saves the player's data. - playerData.clearCooldowns(); - playerData.setKillstreak(0); playerData.saveAll(); // Removes the player's data from the map. @@ -749,6 +754,20 @@ public static void onPlayerMove(@NotNull PlayerMoveEvent event) { boolean playerMoved = (deltaXZ > 0.05 || Math.abs(deltaY) > 0.05); + Vector velocity = player.getVelocity(); + double velocityY = velocity.getY(); + + // Updates the player's on ground ticks. + if (velocityY == -0.0784000015258789) { + long onGroundTicks = playerData.getOnGroundTicks(); + playerData.setOnGroundTicks(onGroundTicks + 1); + } else { + playerData.setOnGroundTicks(0); + } + + // Updates the player's last values. + playerData.setLastVelocityY(velocityY); + // Ignores rotation updates. if (!playerMoved) { return; @@ -768,9 +787,14 @@ public static void onPlayerMove(@NotNull PlayerMoveEvent event) { } // Equips the player's previously used kit when they leave spawn without a kit equipped. - if (playerData.getActiveKit() == null && !player.isDead() && !player.getAllowFlight() - && !Regions.isInSafezone(from)) { + if (playerData.getActiveKit() == null && !player.isDead() + && !player.getAllowFlight() && !Regions.isInSafezone(from)) { player.closeInventory(); + + if (playerData.getPreviousKit() == null) { + playerData.setPreviousKit(new Knight()); + } + playerData.getPreviousKit().apply(player); MessageUtil.messagePlayer(player, "&cYour previous kit has been automatically applied."); return; @@ -783,7 +807,8 @@ public static void onPlayerMove(@NotNull PlayerMoveEvent event) { player.teleport(from); MessageUtil.messagePlayer(player, "&cYou can't enter spawn while combat tagged."); } else { - player.setHealth(20); + double maxHealth = player.getMaxHealth(); + player.setHealth(maxHealth); player.setFireTicks(0); if (!playerData.isNoFall()) { @@ -793,12 +818,48 @@ public static void onPlayerMove(@NotNull PlayerMoveEvent event) { } } + /** + * Handles removing the no-fall effect. + * + * @param event PlayerMoveEvent + */ + @EventHandler(priority = EventPriority.LOWEST) + public static void onNoFallRemoval(@NotNull PlayerMoveEvent event) { + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + + Location to = event.getTo(); + Location from = event.getFrom(); + + double toX = to.getX(); + double toZ = to.getZ(); + + double fromX = from.getX(); + double fromZ = from.getZ(); + + double deltaY = to.getY() - from.getY(); + double deltaXZ = StrictMath.hypot(toX - fromX, toZ - fromZ); + + boolean playerMoved = (deltaXZ > 0.05 || Math.abs(deltaY) > 0.05); + + if (!playerMoved) { + return; + } + + boolean noFall = playerData.isNoFall(); + long onGroundTicks = playerData.getOnGroundTicks(); + + if (noFall && !Regions.isInSafezone(to) && !Regions.isInSafezone(from) && onGroundTicks == 1) { + playerData.setNoFall(false); + } + } + /** * Handles players taking fall damage. * * @param event EntityDamageEvent */ - @EventHandler(ignoreCancelled = true) + @EventHandler public static void onFallDamage(@NotNull EntityDamageEvent event) { if (event.getEntity() instanceof Player) { Player player = (Player) event.getEntity(); diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/ArcherListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/ArcherListener.java index 1be8024..92f75d0 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/ArcherListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/ArcherListener.java @@ -21,9 +21,12 @@ import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; -import net.foulest.kitpvp.util.AbilityUtil; +import net.foulest.kitpvp.kits.type.Archer; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; +import net.foulest.kitpvp.util.TaskUtil; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; @@ -57,29 +60,57 @@ public static void onArcherAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.FEATHER; - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.FEATHER)) { + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Archer)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { return; } // Plays the ability sound. player.getWorld().playSound(playerLoc, Sound.BAT_TAKEOFF, 1, 1); - // Remove the player's existing speed, resistance, and weakness effects. + // Remove the player's existing speed and resistance effects. player.removePotionEffect(PotionEffectType.SPEED); player.removePotionEffect(PotionEffectType.DAMAGE_RESISTANCE); - player.removePotionEffect(PotionEffectType.WEAKNESS); - // Gives the player speed, resistance, weakness, and regeneration. - player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Settings.archerKitDuration * 20, 2, false, false)); - player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Settings.archerKitDuration * 20, 0, false, false)); - player.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, Settings.archerKitDuration * 20, 1, false, false)); - player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Settings.archerKitDuration * 20, 1, false, false)); + // Gives the player speed, resistance, and regeneration. + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, Settings.archerKitDuration * 20, 2, true, true)); + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Settings.archerKitDuration * 20, 0, true, true)); + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, Settings.archerKitDuration * 20, 1, true, true)); + + // Gives the kit's default potion effects back after the ability duration. + int changeCount = playerData.getChangeCount(); + TaskUtil.runTaskLater(() -> { + if (playerData.getChangeCount() == changeCount) { + for (PotionEffect effect : playerKit.getPotionEffects()) { + player.addPotionEffect(effect); + } + } + }, Settings.archerKitDuration * 20L + 1L); // Sets the player's ability cooldown. MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.archerKitCooldown, true); + playerData.setCooldown(playerKit, abilityItem, Settings.archerKitCooldown, true); } } diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/FishermanListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/FishermanListener.java index cd7cac7..10fcdbd 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/FishermanListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/FishermanListener.java @@ -22,16 +22,19 @@ import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.kits.type.Fisherman; import net.foulest.kitpvp.region.Regions; -import net.foulest.kitpvp.util.AbilityUtil; +import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; import org.bukkit.Effect; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.player.PlayerFishEvent; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -41,65 +44,140 @@ public class FishermanListener implements Listener { /** - * Handles Fisherman's ability. + * Handles the Fisherman kit's ability. * - * @param event The event. + * @param event The event being handled. */ - @EventHandler + @EventHandler(ignoreCancelled = true) public static void onFishermanAbility(@NotNull PlayerFishEvent event) { - // Ignores the event if the target is not a player. - if (!(event.getCaught() instanceof Player)) { + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + Kit playerKit = playerData.getActiveKit(); + PlayerFishEvent.State state = event.getState(); + Material rodSpamItem = Material.RAW_FISH; + Material abilityItem = Material.FISHING_ROD; + + // Ignores the event if the player isn't using the Fisherman kit. + if (!(playerKit instanceof Fisherman)) { return; } - Player player = event.getPlayer(); - Player target = (Player) event.getCaught(); - - // Ignores the event if the player hooks themselves. - if (target == player) { - MessageUtil.messagePlayer(player, "&cYou can't hook yourself."); + // Ignores the event if the player is in spawn. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); event.setCancelled(true); return; } - PlayerData targetData = PlayerDataManager.getPlayerData(target); - Location targetLoc = target.getLocation(); - - // Ignores ineligible players. - if (targetData.getActiveKit() == null || Regions.isInSafezone(targetLoc)) { + // Checks if the player has an active rod cooldown. + if (state == PlayerFishEvent.State.FISHING + && playerData.hasCooldown(rodSpamItem, true)) { event.setCancelled(true); return; } - // Marks both players for combat. - CombatTag.markForCombat(player, target); + // Sets the player's rod cooldown if the state is FISHING. + if (state == PlayerFishEvent.State.FISHING) { + playerData.setCooldown(playerKit, rodSpamItem, Settings.fishermanKitRodCooldown, false); + } + + // Handles hooking players with the Fishing Rod. + if (event.getCaught() instanceof Player) { + Player target = (Player) event.getCaught(); + + // Ignores the event if the player hooks themselves. + if (target == player) { + MessageUtil.messagePlayer(player, "&cYou can't hook yourself."); + event.setCancelled(true); + event.getHook().remove(); + return; + } + + PlayerData targetData = PlayerDataManager.getPlayerData(target); + Location targetLoc = target.getLocation(); + + // Ignores ineligible players. + if (targetData.getActiveKit() == null || Regions.isInSafezone(targetLoc)) { + MessageUtil.messagePlayer(player, "&cYou can't hook this player."); + event.setCancelled(true); + event.getHook().remove(); + return; + } + + // Marks both players for combat. + CombatTag.markForCombat(player, target); + + // Handles players reeling other players in. + if (event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { + // Checks if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { + event.setCancelled(true); + return; + } + + // Play the ability sound and effect. + target.getWorld().playSound(targetLoc, Sound.SPLASH2, 1, 1); + target.getWorld().playEffect(targetLoc, Effect.SPLASH, 1); + player.playSound(playerLoc, Sound.SPLASH2, 1, 1); + // Teleports the target to the player's location. + MessageUtil.messagePlayer(target, "&cYou have been hooked by a Fisherman!"); + target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Settings.fishermanKitDuration * 20, 0, false, false)); + event.getCaught().teleport(playerLoc); + + // Sets the player's ability cooldown. + MessageUtil.messagePlayer(player, "&aYour ability has been used."); + playerData.setCooldown(playerKit, abilityItem, Settings.fishermanKitCooldown, true); + } + } + } + + /** + * Handles the Fisherman's Sword ability. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onFishermanSwordHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Player data + Player player = (Player) event.getDamager(); PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); - Kit playerKit = playerData.getActiveKit(); - // Checks for ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit)) { + // Target data + Player target = (Player) event.getEntity(); + PlayerData targetData = PlayerDataManager.getPlayerData(target); + Location targetLoc = target.getLocation(); + + // Ignores the event if the damager is not using the Jester kit. + if (!(playerData.getActiveKit() instanceof Fisherman) + || targetData.getActiveKit() == null + || Regions.isInSafezone(playerLoc) + || Regions.isInSafezone(targetLoc)) { return; } - // Ignores the event if the player isn't using the Fisherman ability. - if (event.getState() != PlayerFishEvent.State.CAUGHT_ENTITY) { + // Ignores hits that aren't with the Fisherman's Sword. + if (player.getItemInHand() == null + || !player.getItemInHand().hasItemMeta() + || !player.getItemInHand().getItemMeta().hasDisplayName() + || !player.getItemInHand().getItemMeta().getDisplayName().contains("Fisherman's Sword")) { return; } - // Play the ability sound. - target.getWorld().playSound(targetLoc, Sound.WATER, 1, 1); - target.getWorld().playEffect(targetLoc, Effect.SPLASH, 1); - player.playSound(playerLoc, Sound.SPLASH2, 1, 1); - - // Teleports the target to the player's location. - MessageUtil.messagePlayer(target, "&cYou have been hooked by a Fisherman!"); - target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Settings.fishermanKitDuration * 20, 0, false, false)); - event.getCaught().teleport(playerLoc); - - // Sets the player's ability cooldown. - MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.fishermanKitCooldown, true); + // Increases the damage dealt by 50% if the target is in water. + if (target.getLocation().getBlock().getType() == Material.WATER + || target.getLocation().getBlock().getType() == Material.STATIONARY_WATER) { + double damage = event.getDamage(); + event.setDamage(damage * 1.5); + target.getWorld().playSound(targetLoc, Sound.SPLASH2, 0.5F, 1); + } } } diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/JesterListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/JesterListener.java new file mode 100644 index 0000000..e28a7a9 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/JesterListener.java @@ -0,0 +1,228 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.listeners.kits; + +import lombok.Data; +import net.foulest.kitpvp.KitPvP; +import net.foulest.kitpvp.data.PlayerData; +import net.foulest.kitpvp.data.PlayerDataManager; +import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.kits.type.Jester; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.ConstantUtil; +import net.foulest.kitpvp.util.MessageUtil; +import net.foulest.kitpvp.util.Settings; +import org.bukkit.Effect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; + +@Data +public class JesterListener implements Listener { + + /** + * Handles the Jester ability. + * + * @param event The event. + */ + @EventHandler + public static void onJesterAbility(@NotNull PlayerInteractEvent event) { + // Ignores the event if the player isn't right-clicking. + if (event.getAction() != Action.RIGHT_CLICK_AIR + && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + Kit playerKit = playerData.getActiveKit(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.STICK; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Jester)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { + return; + } + + // Launches a projectile from the Jester's eyes. + Projectile projectile = player.launchProjectile(Snowball.class); + + // Play a sound effect at the player's location. + player.getWorld().playSound(playerLoc, Sound.STEP_WOOD, 1.0F, 1.0F); + player.getWorld().playSound(playerLoc, Sound.HORSE_WOOD, 1.0F, 1.0F); + player.getWorld().playSound(playerLoc, Sound.SHEEP_WALK, 1.0F, 1.0F); + + // Set the projectile's metadata. + projectile.setMetadata("Jester", new FixedMetadataValue(KitPvP.getInstance(), true)); + + // Sets the player's ability cooldown. + MessageUtil.messagePlayer(player, "&aYour ability has been used."); + playerData.setCooldown(playerKit, abilityItem, Settings.jesterKitCooldown, true); + } + + /** + * Handles the Jester's projectile hitting the ground or players. + * + * @param event The event. + */ + @EventHandler + public static void onJesterProjectileHit(@NotNull ProjectileHitEvent event) { + Projectile entity = event.getEntity(); + + // Ignores the event if the projectile is not from the Jester's ability. + if (!entity.hasMetadata("Jester")) { + return; + } + + // Play glass breaking sounds where the ornament hit. + Location location = entity.getLocation(); + entity.getWorld().playEffect(location, Effect.STEP_SOUND, 20); + + // Remove the projectile. + entity.remove(); + } + + /** + * Handles the Jester's projectile hitting an entity. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onJesterProjectileHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager is not a projectile. + if (!(event.getDamager() instanceof Projectile)) { + return; + } + + Projectile projectile = (Projectile) event.getDamager(); + + // Ignores the event if the projectile is not from the Jester's ability. + if (!projectile.hasMetadata("Jester")) { + return; + } + + // Ignores the event if the entity is not a living entity. + if (!(event.getEntity() instanceof LivingEntity)) { + return; + } + + // Ignores the event if the shooter is not a player. + if (!(projectile.getShooter() instanceof Player)) { + return; + } + + // Player data + LivingEntity target = (LivingEntity) event.getEntity(); + + // Deal damage to the target. + event.setDamage(Settings.jesterKitDamage); + + // Inflict Wither III on the target. + target.addPotionEffect(new PotionEffect(PotionEffectType.WITHER, Settings.jesterKitDuration * 20, 2)); + + // Remove the projectile. + projectile.remove(); + } + + /** + * Handles the Jester's Sword ability. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onJesterSwordHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Player data + Player player = (Player) event.getDamager(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + + // Target data + Player target = (Player) event.getEntity(); + PlayerData targetData = PlayerDataManager.getPlayerData(target); + Location targetLoc = target.getLocation(); + + // Ignores the event if the damager is not using the Jester kit. + if (!(playerData.getActiveKit() instanceof Jester) + || targetData.getActiveKit() == null + || Regions.isInSafezone(playerLoc) + || Regions.isInSafezone(targetLoc)) { + return; + } + + // Ignores hits that aren't with the Jester's Sword. + if (player.getItemInHand() == null + || !player.getItemInHand().hasItemMeta() + || !player.getItemInHand().getItemMeta().hasDisplayName() + || !player.getItemInHand().getItemMeta().getDisplayName().contains("Jester's Sword")) { + return; + } + + double health = player.getHealth(); + double maxHealth = player.getMaxHealth(); + double damage = event.getDamage(); + + // If a player's health is <50%, deal 15% more damage. + if (health / maxHealth < 0.5) { + event.setDamage(damage * 1.15); + player.getWorld().playSound(targetLoc, Sound.SILVERFISH_HIT, 0.5F, 1.0F); + } else { + event.setDamage(damage * 0.85); + } + } +} diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/KangarooListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/KangarooListener.java index 794395c..4d8f5c6 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/KangarooListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/KangarooListener.java @@ -23,7 +23,9 @@ import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Kangaroo; import net.foulest.kitpvp.region.Regions; -import net.foulest.kitpvp.util.*; +import net.foulest.kitpvp.util.ConstantUtil; +import net.foulest.kitpvp.util.MessageUtil; +import net.foulest.kitpvp.util.Settings; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Sound; @@ -57,28 +59,30 @@ public static void onKangarooAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); - - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.FIREWORK)) { + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.FIREWORK; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { return; } - // Ignores the event if the player isn't using the Kangaroo ability. - if (!(playerKit instanceof Kangaroo) - || !event.getAction().toString().contains("RIGHT") - || player.getItemInHand().getType() != Material.FIREWORK) { + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Kangaroo)) { return; } - // Ignores the event if the player is in spawn. + // Ignores the event if the player is in a safe zone. if (Regions.isInSafezone(playerLoc)) { MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); return; } // Ignores the event if the player's ability is on cooldown. - if (playerData.hasCooldown(true)) { + if (playerData.hasCooldown(abilityItem, true)) { return; } @@ -89,9 +93,9 @@ public static void onKangarooAbility(@NotNull PlayerInteractEvent event) { Vector direction = player.getEyeLocation().getDirection(); // Adjusts the direction based on whether the player is sneaking. - if (BlockUtil.isOnGroundOffset(player, 0.1)) { - direction.setY(0.3); - direction.multiply(2.5); + if (player.isSneaking() && player.getVelocity().getY() != -0.0784000015258789) { + direction.setY(0.2); + direction.multiply(2.25); } else { direction.setY(1.2); } @@ -102,7 +106,7 @@ public static void onKangarooAbility(@NotNull PlayerInteractEvent event) { // Sets the player's ability cooldown. MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.kangarooKitCooldown, true); + playerData.setCooldown(playerKit, abilityItem, Settings.kangarooKitCooldown, true); } /** @@ -122,6 +126,8 @@ public static void onMarketGardenerHit(@NotNull EntityDamageByEntityEvent event) Player player = (Player) event.getDamager(); PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); + ItemStack itemInHand = player.getItemInHand(); + Material abilityItem = Material.FIREWORK; // Target data Player target = (Player) event.getEntity(); @@ -138,10 +144,10 @@ public static void onMarketGardenerHit(@NotNull EntityDamageByEntityEvent event) } // Ignores hits that aren't with the Market Gardener. - if (player.getItemInHand() == null - || !player.getItemInHand().hasItemMeta() - || !player.getItemInHand().getItemMeta().hasDisplayName() - || !player.getItemInHand().getItemMeta().getDisplayName().contains("Market Gardener")) { + if (itemInHand == null + || !itemInHand.hasItemMeta() + || !itemInHand.getItemMeta().hasDisplayName() + || !itemInHand.getItemMeta().getDisplayName().contains("Market Gardener")) { return; } @@ -150,8 +156,8 @@ public static void onMarketGardenerHit(@NotNull EntityDamageByEntityEvent event) // 1a. Deal +150% damage to the target. // 1. If the player is airborne and has a cooldown... - if (playerData.isNoFall() && playerData.hasCooldown(false)) { - // 1a. Deal +150% damage to the target. + if (playerData.isNoFall() && playerData.hasCooldown(abilityItem, false)) { + // 1a. Deal +200% damage to the target. MessageUtil.messagePlayer(player, "&aYou landed a critical hit on &e" + targetName + "&a!"); player.getWorld().playSound(playerLoc, Sound.ITEM_BREAK, 1, 1); double damage = event.getDamage(); diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/MageListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/MageListener.java index 131ffa8..eef8a04 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/MageListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/MageListener.java @@ -23,9 +23,8 @@ import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Mage; import net.foulest.kitpvp.kits.type.Pyro; -import net.foulest.kitpvp.util.AbilityUtil; -import net.foulest.kitpvp.util.MessageUtil; -import net.foulest.kitpvp.util.Settings; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.*; import org.bukkit.Effect; import org.bukkit.Location; import org.bukkit.Material; @@ -64,10 +63,30 @@ public static void onMageAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.GLOWSTONE_DUST; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Mage)) { + return; + } - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.GLOWSTONE_DUST)) { + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { return; } @@ -78,11 +97,13 @@ public static void onMageAbility(@NotNull PlayerInteractEvent event) { if (nearbyPlayers.isEmpty()) { player.playSound(playerLoc, Sound.VILLAGER_NO, 1, 1); MessageUtil.messagePlayer(player, "&cAbility failed: no players nearby."); - playerData.setCooldown(playerKit, 3, true); + playerData.setCooldown(playerKit, abilityItem, 3, true); return; } for (Player target : nearbyPlayers) { + PlayerData targetData = PlayerDataManager.getPlayerData(target); + Kit targetKit = targetData.getActiveKit(); Location targetLoc = target.getLocation(); // Give the target Slowness, Blindness, and Weakness for 3 seconds. @@ -94,12 +115,22 @@ public static void onMageAbility(@NotNull PlayerInteractEvent event) { target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, Settings.mageKitDuration * 20, 1, false, false)); target.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, Settings.mageKitDuration * 20, 1, false, false)); MessageUtil.messagePlayer(target, "&cYou have been debuffed by a Mage!"); + + // Gives the kit's default potion effects back after the ability duration. + int changeCount = targetData.getChangeCount(); + TaskUtil.runTaskLater(() -> { + if (targetData.getChangeCount() == changeCount) { + for (PotionEffect effect : targetKit.getPotionEffects()) { + target.addPotionEffect(effect); + } + } + }, Settings.mageKitDuration * 20L + 1L); } // Sets the player's ability cooldown. player.playSound(playerLoc, Sound.FIZZ, 1, 1); MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.mageKitCooldown, true); + playerData.setCooldown(playerKit, abilityItem, Settings.mageKitCooldown, true); } /** diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/NinjaListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/NinjaListener.java index 5e39add..1ec029e 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/NinjaListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/NinjaListener.java @@ -20,93 +20,19 @@ import lombok.Data; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; -import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Ninja; -import net.foulest.kitpvp.util.AbilityUtil; -import net.foulest.kitpvp.util.MessageUtil; -import net.foulest.kitpvp.util.Settings; -import net.foulest.kitpvp.util.TaskUtil; -import org.bukkit.*; +import org.bukkit.Location; +import org.bukkit.Sound; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; @Data public class NinjaListener implements Listener { - /** - * Handles the Ninja ability. - * - * @param event The event. - */ - @EventHandler - public static void onNinjaAbility(@NotNull PlayerInteractEvent event) { - // Ignores the event if the player isn't right-clicking. - if (event.getAction() != Action.RIGHT_CLICK_AIR - && event.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - Player player = event.getPlayer(); - PlayerData playerData = PlayerDataManager.getPlayerData(player); - Location playerLoc = player.getLocation(); - Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); - - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.INK_SACK)) { - return; - } - - // Gives the player Invisibility and Jump Boost III. - player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, - Settings.ninjaKitDuration * 20, 0, false, false)); - player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, - Settings.ninjaKitDuration * 20, 2, false, false)); - - // Plays a smoke sound effect. - player.getWorld().playEffect(playerLoc, Effect.LARGE_SMOKE, 1, 1); - - // Hides the player from other players. - for (Player target : Bukkit.getOnlinePlayers()) { - if (target == player) { - continue; - } - - target.hidePlayer(player); - } - - // Create a task that restores the player's visibility. - TaskUtil.runTaskLater(() -> { - if (playerData.getActiveKit() instanceof Ninja) { - player.getWorld().playSound(playerLoc, Sound.BAT_IDLE, 1, 1); - player.removePotionEffect(PotionEffectType.JUMP); - MessageUtil.messagePlayer(player, "&cYou are no longer invisible."); - - for (Player target : Bukkit.getOnlinePlayers()) { - if (target == player) { - continue; - } - - target.showPlayer(player); - } - } - }, Settings.ninjaKitDuration * 20L); - - // Sets the player's ability cooldown. - player.getWorld().playSound(playerLoc, Sound.BAT_DEATH, 1, 1); - MessageUtil.messagePlayer(player, "&aYou are now invisible."); - playerData.setCooldown(playerKit, Settings.ninjaKitCooldown, true); - } - /** * Handles Ninjas damaging players with their blade. * @@ -165,31 +91,4 @@ public static void onNinjaBladeHit(@NotNull EntityDamageByEntityEvent event) { event.setDamage(damage * 1.5); } } - - /** - * Handles Ninjas damaging players while invisible. - * - * @param event EntityDamageByEntityEvent - */ - @EventHandler(ignoreCancelled = true) - public static void onNinjaInvisHit(@NotNull EntityDamageByEntityEvent event) { - Entity damagerEntity = event.getDamager(); - Entity targetEntity = event.getEntity(); - - // Checks if the entities are both players. - if (!(damagerEntity instanceof Player) - || !(targetEntity instanceof Player)) { - return; - } - - Player damager = (Player) damagerEntity; - PlayerData damagerData = PlayerDataManager.getPlayerData(damager); - - // Cancels hits while Ninja is invisible. - if (damagerData.getActiveKit() instanceof Ninja - && damager.hasPotionEffect(PotionEffectType.INVISIBILITY)) { - MessageUtil.messagePlayer(damager, "&cYou can't damage other players while invisible."); - event.setCancelled(true); - } - } } diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/PyroListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/PyroListener.java index dc8228e..3cb98b9 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/PyroListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/PyroListener.java @@ -18,12 +18,13 @@ package net.foulest.kitpvp.listeners.kits; import lombok.Data; +import net.foulest.kitpvp.KitPvP; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Pyro; import net.foulest.kitpvp.region.Regions; -import net.foulest.kitpvp.util.AbilityUtil; +import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; import org.bukkit.Effect; @@ -31,19 +32,22 @@ import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; -import java.util.Collection; - @Data public class PyroListener implements Listener { @@ -64,45 +68,181 @@ public static void onPyroAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.FLINT_AND_STEEL; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.FIREBALL)) { + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Pyro)) { return; } - // Gets the nearby players within a 5 block radius. - Collection nearbyPlayers = AbilityUtil.getNearbyPlayers(player, 5, 5, 5); + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } - // Ignores the event if there are no players nearby. - if (nearbyPlayers.isEmpty()) { - player.playSound(playerLoc, Sound.VILLAGER_NO, 1, 1); - MessageUtil.messagePlayer(player, "&cAbility failed: no players nearby."); - playerData.setCooldown(playerKit, 3, true); + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { return; } - // Play the ability sound and effect. - player.getWorld().playSound(playerLoc, Sound.GHAST_FIREBALL, 1, 1); - player.getWorld().playEffect(playerLoc, Effect.MOBSPAWNER_FLAMES, 1); + // Cancel the event to prevent the player from using the flint and steel. + event.setCancelled(true); - for (Player target : nearbyPlayers) { - Location targetLoc = target.getLocation(); + // Launches a projectile just to get the velocity, then removes it. + Projectile flare = player.launchProjectile(Snowball.class); + flare.setFireTicks(99999); - // Lights targets on fire. - target.setFireTicks(Settings.pyroKitDuration * 20); + // Play a sound effect at the player's location. + player.getWorld().playSound(playerLoc, Sound.FIREWORK_LAUNCH, 1.0F, 1.0F); + player.getWorld().playSound(playerLoc, Sound.FIREWORK_BLAST, 1.0F, 1.0F); - // Play a sound to the target. - target.playSound(targetLoc, Sound.GHAST_FIREBALL, 1, 1); - target.getWorld().playEffect(targetLoc, Effect.MOBSPAWNER_FLAMES, 1); - MessageUtil.messagePlayer(target, "&cYou have been set on fire by a Pyro!"); - } + // Set the projectile's metadata. + flare.setMetadata("Pyro", new FixedMetadataValue(KitPvP.getInstance(), true)); // Sets the player's ability cooldown. MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.pyroKitCooldown, true); + playerData.setCooldown(playerKit, abilityItem, Settings.pyroKitCooldown, true); + } + + /** + * Handles the Pyro's flare hitting the ground or players. + * + * @param event The event. + */ + @EventHandler + public static void onPyroFlareHit(@NotNull ProjectileHitEvent event) { + Projectile entity = event.getEntity(); + + // Ignores the event if the projectile is not from the Pyro's ability. + if (!entity.hasMetadata("Pyro")) { + return; + } + + // Play fire and sizzling effects at the projectile's location. + // Essentially, a flare hitting the ground. + Location location = entity.getLocation(); + entity.getWorld().playEffect(location, Effect.MOBSPAWNER_FLAMES, 1); + entity.getWorld().playEffect(location, Effect.SMOKE, 1); + entity.getWorld().playSound(location, Sound.FIZZ, 1, 1); + + // Remove the projectile. + entity.remove(); + } + + /** + * Handles the Pyro's flare hitting an entity. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onPyroFlareHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager is not a projectile. + if (!(event.getDamager() instanceof Projectile)) { + return; + } + + Projectile projectile = (Projectile) event.getDamager(); + + // Ignores the event if the projectile is not from the Pyro's ability. + if (!projectile.hasMetadata("Pyro")) { + return; + } + + // Ignores the event if the entity is not a player. + if (!(event.getEntity() instanceof Player)) { + return; + } + + // Ignores the event if the shooter is not a player. + if (!(projectile.getShooter() instanceof Player)) { + return; + } + + // Player data + Player target = (Player) event.getEntity(); + String targetName = target.getName(); + Player shooter = (Player) projectile.getShooter(); + + // Deal damage to the target. + event.setDamage(Settings.pyroKitDamage); + + // Inflict fire damage to the target. + target.setFireTicks(Settings.pyroKitDuration * 20); + + // Send messages to the players. + MessageUtil.messagePlayer(target, "&cYou have been hit by the Pyro's flare!"); + MessageUtil.messagePlayer(shooter, "&aYou hit &e" + targetName + " &awith your flare!"); + + // Remove the projectile. + projectile.remove(); } +// /** +// * Handles the Pyro ability. +// * +// * @param event The event. +// */ +// @EventHandler +// public static void onPyroAbility(@NotNull PlayerInteractEvent event) { +// // Ignores the event if the player isn't right-clicking. +// if (event.getAction() != Action.RIGHT_CLICK_AIR +// && event.getAction() != Action.RIGHT_CLICK_BLOCK) { +// return; +// } +// +// Player player = event.getPlayer(); +// PlayerData playerData = PlayerDataManager.getPlayerData(player); +// Location playerLoc = player.getLocation(); +// Kit playerKit = playerData.getActiveKit(); +// ItemStack item = event.getItem(); +// +// // Checks for common ability exclusions. +// if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.FIREBALL)) { +// return; +// } +// +// // Gets the nearby players within a 5 block radius. +// Collection nearbyPlayers = AbilityUtil.getNearbyPlayers(player, 5, 5, 5); +// +// // Ignores the event if there are no players nearby. +// if (nearbyPlayers.isEmpty()) { +// player.playSound(playerLoc, Sound.VILLAGER_NO, 1, 1); +// MessageUtil.messagePlayer(player, "&cAbility failed: no players nearby."); +// playerData.setCooldown(playerKit, Material.FIREBALL, 3, true); +// return; +// } +// +// // Play the ability sound and effect. +// player.getWorld().playSound(playerLoc, Sound.GHAST_FIREBALL, 1, 1); +// player.getWorld().playEffect(playerLoc, Effect.MOBSPAWNER_FLAMES, 1); +// +// for (Player target : nearbyPlayers) { +// Location targetLoc = target.getLocation(); +// +// // Lights targets on fire. +// target.setFireTicks(Settings.pyroKitDuration * 20); +// +// // Play a sound to the target. +// target.playSound(targetLoc, Sound.GHAST_FIREBALL, 1, 1); +// target.getWorld().playEffect(targetLoc, Effect.MOBSPAWNER_FLAMES, 1); +// MessageUtil.messagePlayer(target, "&cYou have been set on fire by a Pyro!"); +// } +// +// // Sets the player's ability cooldown. +// MessageUtil.messagePlayer(player, "&aYour ability has been used."); +// playerData.setCooldown(playerKit, Material.FIREBALL, Settings.pyroKitCooldown, true); +// } + /** * Handles the Axtinguisher ability. * @@ -168,9 +308,10 @@ public static void onAxtinguisherHit(@NotNull EntityDamageByEntityEvent event) { target.getWorld().playEffect(targetLoc, Effect.CRIT, 1); // 2. If the hit was a kill, give the Pyro a speed boost. - if (target.getHealth() - event.getFinalDamage() - Settings.pyroKitDamage <= 0) { - MessageUtil.messagePlayer(player, "&aYou have been given a speed boost on kill!"); - player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 3 * 20, 1, false, false)); + if (target.getHealth() - event.getFinalDamage() - Settings.pyroKitDamage < -4.0 + && event.getCause() == EntityDamageEvent.DamageCause.ENTITY_ATTACK) { + MessageUtil.messagePlayer(player, "&aYou have been given a speed boost on kill! | Target Health: " + (target.getHealth() - event.getFinalDamage() - Settings.pyroKitDamage)); + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 3 * 20, 1, true, true)); } } } diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/ReaperListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/ReaperListener.java new file mode 100644 index 0000000..773bca5 --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/ReaperListener.java @@ -0,0 +1,286 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.listeners.kits; + +import lombok.Data; +import net.foulest.kitpvp.KitPvP; +import net.foulest.kitpvp.data.PlayerData; +import net.foulest.kitpvp.data.PlayerDataManager; +import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.kits.type.Reaper; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.ConstantUtil; +import net.foulest.kitpvp.util.MessageUtil; +import net.foulest.kitpvp.util.Settings; +import net.foulest.kitpvp.util.TaskUtil; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; + +@Data +public class ReaperListener implements Listener { + + /** + * Handles the Reaper ability. + * + * @param event The event. + */ + @EventHandler + public static void onReaperAbility(@NotNull PlayerInteractEvent event) { + // Ignores the event if the player isn't right-clicking. + if (event.getAction() != Action.RIGHT_CLICK_AIR + && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Player activeReaperMark = playerData.getActiveReaperMark(); + Location playerLoc = player.getLocation(); + Kit playerKit = playerData.getActiveKit(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.PAPER; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Reaper)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { + return; + } + + // Check if the player has any active marks. + if (activeReaperMark == null) { + MessageUtil.messagePlayer(player, "&cYou do not have an active mark."); + return; + } + + // Removes the mark from the player. + removeReaperMark(playerData, true, true); + + // Play the ability sound. + player.getWorld().playSound(playerLoc, Sound.CREEPER_DEATH, 1, 1); + + // Sets the player's ability cooldown. + MessageUtil.messagePlayer(player, "&aYour active mark has been cleared."); + playerData.setCooldown(playerKit, abilityItem, Settings.reaperKitCooldown, false); + } + + /** + * Handles the Reaper's Scythe ability. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onScytheHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Player data + Player player = (Player) event.getDamager(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + + // Target data + Player target = (Player) event.getEntity(); + PlayerData targetData = PlayerDataManager.getPlayerData(target); + Location targetLoc = target.getLocation(); + String targetName = target.getName(); + + // Ignores the event if the damager is not using the Reaper kit. + if (!(playerData.getActiveKit() instanceof Reaper) + || targetData.getActiveKit() == null + || Regions.isInSafezone(playerLoc) + || Regions.isInSafezone(targetLoc)) { + return; + } + + // Ignores hits that aren't with Reaper's Scythe. + if (player.getItemInHand() == null + || !player.getItemInHand().hasItemMeta() + || !player.getItemInHand().getItemMeta().hasDisplayName() + || !player.getItemInHand().getItemMeta().getDisplayName().contains("Reaper's Scythe")) { + return; + } + + // ------------------------------------------------ + // When a player is hit by a Reaper... + // 1. Check if the Reaper has an active mark. + // 1a. If the Reaper has an active mark, do nothing. + // 1b. If the Reaper does not have an active mark, apply a mark to the target. + // ------------------------------------------------ + + // Check if the Reaper has an active mark. + if (playerData.getActiveReaperMark() != null) { + return; + } + + // Check if that player is already marked. + if (target.hasMetadata("reaperMark")) { + return; + } + + // Apply a mark to the target. + playerData.setActiveReaperMark(target); + MessageUtil.messagePlayer(player, "&aYou have marked &e" + targetName + " &aas your target!"); + MessageUtil.messagePlayer(target, "&cYou have been marked by a Reaper!"); + + // Set the mark metadata on the target. + target.setMetadata("reaperMark", new FixedMetadataValue(KitPvP.getInstance(), true)); + + // Remove the mark after the designated time. + TaskUtil.runTaskLater(() -> { + if (target.hasMetadata("reaperMark")) { + MessageUtil.messagePlayer(player, "&aYour mark on &e" + targetName + " &ahas expired."); + removeReaperMark(targetData, false, true); + } + }, Settings.reaperKitDuration * 20L); + + // Play the ability sounds. + target.getWorld().playSound(targetLoc, Sound.SKELETON_WALK, 1, 1); + target.getWorld().playSound(targetLoc, Sound.SHEEP_SHEAR, 1, 1); + + // Give the target one second of Blindness if they don't have it. + if (!target.hasPotionEffect(PotionEffectType.BLINDNESS)) { + target.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20, 0)); + } + } + + /** + * Handles the Reaper's Mark hit event. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onMarkHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Damager data + Player damager = (Player) event.getDamager(); + PlayerData damagerData = PlayerDataManager.getPlayerData(damager); + + // Target data + Player target = (Player) event.getEntity(); + + // Set the damage dealt by the Scythe to 4 hearts. + if (damagerData.getActiveKit() instanceof Reaper + && damager.getItemInHand() != null + && damager.getItemInHand().hasItemMeta() + && damager.getItemInHand().getItemMeta().hasDisplayName() + && damager.getItemInHand().getItemMeta().getDisplayName().contains("Reaper's Scythe")) { + event.setDamage(4.0); + } + + // Ignores the event if the target does not have a mark. + if (target.getMetadata("reaperMark").isEmpty()) { + return; + } + + // Applies a 50% damage increase to the target. + double damage = event.getDamage(); + event.setDamage(damage * 1.5); + + // Play the ability sound. + Location location = target.getLocation(); + target.getWorld().playSound(location, Sound.SKELETON_HURT, 0.5F, 1); + } + + /** + * Removes the Reaper mark from the player. + * + * @param playerData The player's data. + * @param isPlayerDataReaper If the PlayerData object is the Reaper. + */ + public static void removeReaperMark(@NotNull PlayerData playerData, boolean isPlayerDataReaper, boolean messageTarget) { + Player player = playerData.getPlayer(); + Player reaperMark = playerData.getActiveReaperMark(); + + // Removes marks if you are the Reaper. + if (isPlayerDataReaper && reaperMark != null) { + Location location = reaperMark.getLocation(); + reaperMark.getWorld().playSound(location, Sound.FIZZ, 1, 1); + reaperMark.removeMetadata("reaperMark", KitPvP.getInstance()); + playerData.setActiveReaperMark(null); + + if (messageTarget) { + if (!Regions.isInSafezone(location)) { + MessageUtil.messagePlayer(reaperMark, "&aYou are no longer marked by a Reaper."); + } + + MessageUtil.messagePlayer(player, "&aYour active mark has been cleared."); + } + } + + // Removes marks if you are the target. + if (!isPlayerDataReaper && player.hasMetadata("reaperMark")) { + Location location = player.getLocation(); + player.getWorld().playSound(location, Sound.FIZZ, 1, 1); + player.removeMetadata("reaperMark", KitPvP.getInstance()); + + if (messageTarget && !Regions.isInSafezone(location)) { + MessageUtil.messagePlayer(player, "&aYou are no longer marked by a Reaper."); + } + + // Iterates through all online players to find the Reaper who marked the target. + for (Player onlinePlayer : KitPvP.getInstance().getServer().getOnlinePlayers()) { + PlayerData onlinePlayerData = PlayerDataManager.getPlayerData(onlinePlayer); + Player activeReaperMark = onlinePlayerData.getActiveReaperMark(); + + if (player.equals(activeReaperMark)) { + onlinePlayerData.setActiveReaperMark(null); + MessageUtil.messagePlayer(onlinePlayer, "&aYour active mark has been cleared."); + break; + } + } + } + } +} diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/SoldierListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/SoldierListener.java new file mode 100644 index 0000000..909970b --- /dev/null +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/SoldierListener.java @@ -0,0 +1,281 @@ +/* + * KitPvP - a fully-featured core plugin for the KitPvP gamemode. + * Copyright (C) 2024 Foulest (https://github.com/Foulest) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.foulest.kitpvp.listeners.kits; + +import lombok.Data; +import net.foulest.kitpvp.KitPvP; +import net.foulest.kitpvp.data.PlayerData; +import net.foulest.kitpvp.data.PlayerDataManager; +import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.kits.type.Soldier; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.ConstantUtil; +import net.foulest.kitpvp.util.MessageUtil; +import net.foulest.kitpvp.util.Settings; +import net.foulest.kitpvp.util.TaskUtil; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; + +@Data +public class SoldierListener implements Listener { + + /** + * Handles the Soldier's rage build-up on hit. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onRageBuild(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Damager data + Player damager = (Player) event.getDamager(); + PlayerData damagerData = PlayerDataManager.getPlayerData(damager); + Kit playerKit = damagerData.getActiveKit(); + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Soldier)) { + return; + } + + // Ignores the event if the damager's rage meter is already full. + if (damagerData.getSoldierRage() >= Settings.soldierKitMaxRage) { + return; + } + + double damage = event.getDamage(); + double soldierRage = damagerData.getSoldierRage(); + + // Adds the damage dealt to the damager's rage meter. + damagerData.setSoldierRage(Math.min(soldierRage + damage, Settings.soldierKitMaxRage)); + + // Sends a message to the damager if their rage meter is full. + if (damagerData.getSoldierRage() >= Settings.soldierKitMaxRage) { + Location location = damager.getLocation(); + damager.playSound(location, Sound.WOLF_GROWL, 0.5F, 1.0F); + MessageUtil.messagePlayer(damager, "&eYour rage is fully built and ready for use!"); + } + } + + /** + * Handles the Soldier's Sword hit event. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onSoldierSwordHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Damager data + Player damager = (Player) event.getDamager(); + PlayerData damagerData = PlayerDataManager.getPlayerData(damager); + + // Give the damager a speed boost on hit. + if (damagerData.getActiveKit() instanceof Soldier + && damager.getItemInHand() != null + && damager.getItemInHand().hasItemMeta() + && damager.getItemInHand().getItemMeta().hasDisplayName() + && damager.getItemInHand().getItemMeta().getDisplayName().contains("Soldier's Sword")) { + damager.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 0, false, false)); + } + } + + /** + * Handles the Buff Banner hit event. + * + * @param event The event. + */ + @EventHandler(ignoreCancelled = true) + public static void onBuffBannerHit(@NotNull EntityDamageByEntityEvent event) { + // Ignores the event if the damager or target is not a player. + if (!(event.getDamager() instanceof Player) + || !(event.getEntity() instanceof Player)) { + return; + } + + // Damager data + Player damager = (Player) event.getDamager(); + + // Ignores the event if the damager doesn't have the Buff Banner metadata. + if (!damager.hasMetadata("buffBanner")) { + return; + } + + double damage = event.getDamage(); + + // Adds 50% damage to the hit. + event.setDamage(damage * 1.5); + } + + /** + * Handles the Buff Banner ability. + * + * @param event The event. + */ + @EventHandler + public static void onBuffBannerAbility(@NotNull PlayerInteractEvent event) { + // Ignores the event if the player isn't right-clicking. + if (event.getAction() != Action.RIGHT_CLICK_AIR + && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + Kit playerKit = playerData.getActiveKit(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.BOAT; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Soldier)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Checks if the player has a full rage meter. + if (playerData.getSoldierRage() < Settings.soldierKitMaxRage) { + int percentage = (int) ((playerData.getSoldierRage() / Settings.soldierKitMaxRage) * 100); + MessageUtil.messagePlayer(player, "&cYou need to build up your rage meter! (" + percentage + "%)"); + return; + } + + // Sets the player's rage meter to 0. + playerData.setSoldierRage(0); + + // Play the ability sound. + MessageUtil.messagePlayer(player, "&eYou have activated your Buff Banner!"); + player.getWorld().playSound(playerLoc, Sound.ZOMBIE_PIG_ANGRY, 1, 1); + + // Sets the 'buffBanner' metadata on the player. + player.setMetadata("buffBanner", new FixedMetadataValue(KitPvP.getInstance(), true)); + + // Remove the metadata after the designated time. + TaskUtil.runTaskLater(() -> { + if (player.hasMetadata("buffBanner")) { + player.removeMetadata("buffBanner", KitPvP.getInstance()); + player.playSound(playerLoc, Sound.CREEPER_DEATH, 1, 1); + MessageUtil.messagePlayer(player, "&cYour rage meter is back to normal."); + } + }, Settings.soldierKitBannerDuration * 20L); + } + + /** + * Handles the Battalion's Backup ability. + * + * @param event The event. + */ + @EventHandler + public static void onBattalionAbility(@NotNull PlayerInteractEvent event) { + // Ignores the event if the player isn't right-clicking. + if (event.getAction() != Action.RIGHT_CLICK_AIR + && event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player player = event.getPlayer(); + PlayerData playerData = PlayerDataManager.getPlayerData(player); + Location playerLoc = player.getLocation(); + Kit playerKit = playerData.getActiveKit(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.POWERED_MINECART; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Soldier)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Checks if the player has a full rage meter. + if (playerData.getSoldierRage() < Settings.soldierKitMaxRage) { + int percentage = (int) ((playerData.getSoldierRage() / Settings.soldierKitMaxRage) * 100); + MessageUtil.messagePlayer(player, "&cYou need to build up your rage meter! (" + percentage + "%)"); + return; + } + + // Sets the player's rage meter to 0. + playerData.setSoldierRage(0); + + // Play the ability sound. + MessageUtil.messagePlayer(player, "&eYou have activated your Battalion's Backup!"); + player.getWorld().playSound(playerLoc, Sound.ZOMBIE_REMEDY, 1, 1); + + // Give the player the resistance and absorption effects. + player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, Settings.soldierKitBattalionDuration * 20, 0, true, true)); + player.addPotionEffect(new PotionEffect(PotionEffectType.ABSORPTION, Settings.soldierKitBattalionDuration * 20, 2, true, true)); + + // Gives the kit's default potion effects back after the ability duration. + int changeCount = playerData.getChangeCount(); + TaskUtil.runTaskLater(() -> { + player.playSound(playerLoc, Sound.CREEPER_DEATH, 1, 1); + MessageUtil.messagePlayer(player, "&cYour rage meter is back to normal."); + + if (playerData.getChangeCount() == changeCount) { + for (PotionEffect effect : playerKit.getPotionEffects()) { + player.addPotionEffect(effect); + } + } + }, Settings.archerKitDuration * 20L + 1L); + } +} diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/TankListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/TankListener.java index 1dcab17..e8f8d7b 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/TankListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/TankListener.java @@ -22,7 +22,8 @@ import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Tank; -import net.foulest.kitpvp.util.AbilityUtil; +import net.foulest.kitpvp.region.Regions; +import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; import net.foulest.kitpvp.util.TaskUtil; @@ -62,10 +63,30 @@ public static void onTankAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.ANVIL; + + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { + return; + } + + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Tank)) { + return; + } - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.ANVIL)) { + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { return; } @@ -97,7 +118,7 @@ public static void onTankAbility(@NotNull PlayerInteractEvent event) { // 4. Sets the player's ability cooldown. MessageUtil.messagePlayer(player, "&aYour ability has been used."); - playerData.setCooldown(playerKit, Settings.tankKitCooldown, true); + playerData.setCooldown(playerKit, abilityItem, Settings.tankKitCooldown, true); } /** @@ -137,6 +158,8 @@ public static void onTankShovelHit(@NotNull EntityDamageByEntityEvent event) { double damage = event.getDamage(); event.setDamage(damage * 1.4); + // TODO: Play sound to indicate the Tank takes more melee damage. + } else if (event.getDamager() instanceof Arrow) { Arrow arrow = (Arrow) event.getDamager(); @@ -154,6 +177,10 @@ public static void onTankShovelHit(@NotNull EntityDamageByEntityEvent event) { // 3. If the source is ranged, decrease the damage by 40%. double damage = event.getDamage(); event.setDamage(damage * 0.6); + + // Plays the sound for the Tank's Shovel. + Location location = receiver.getLocation(); + receiver.getWorld().playSound(location, Sound.ZOMBIE_METAL, 0.5F, 1); } } } diff --git a/src/main/java/net/foulest/kitpvp/listeners/kits/VampireListener.java b/src/main/java/net/foulest/kitpvp/listeners/kits/VampireListener.java index da851f3..aec0a10 100644 --- a/src/main/java/net/foulest/kitpvp/listeners/kits/VampireListener.java +++ b/src/main/java/net/foulest/kitpvp/listeners/kits/VampireListener.java @@ -18,19 +18,17 @@ package net.foulest.kitpvp.listeners.kits; import lombok.Data; -import net.foulest.kitpvp.KitPvP; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; import net.foulest.kitpvp.kits.type.Vampire; import net.foulest.kitpvp.region.Regions; -import net.foulest.kitpvp.util.AbilityUtil; +import net.foulest.kitpvp.util.ConstantUtil; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; import net.foulest.kitpvp.util.TaskUtil; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; +import org.bukkit.*; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -38,20 +36,126 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; @Data public class VampireListener implements Listener { +// /** +// * Handles the Vampire ability. +// * +// * @param event The event. +// */ +// @EventHandler +// @SuppressWarnings("NestedMethodCall") +// public static void onVampireAbility(@NotNull PlayerInteractEvent event) { +// // Ignores the event if the player isn't right-clicking. +// if (event.getAction() != Action.RIGHT_CLICK_AIR +// && event.getAction() != Action.RIGHT_CLICK_BLOCK) { +// return; +// } +// +// Player player = event.getPlayer(); +// PlayerData playerData = PlayerDataManager.getPlayerData(player); +// Location playerLoc = player.getLocation(); +// Kit playerKit = playerData.getActiveKit(); +// ItemStack itemStack = event.getItem(); +// Material abilityItem = Material.REDSTONE; +// +// // Ignores the event if the given item does not match the desired item. +// if (itemStack == null +// || !itemStack.hasItemMeta() +// || !itemStack.getItemMeta().hasDisplayName() +// || itemStack.getType() != abilityItem) { +// return; +// } +// +// // Ignores the event if the player is not using the desired kit. +// if (!(playerKit instanceof Vampire)) { +// return; +// } +// +// // Ignores the event if the player is in a safe zone. +// if (Regions.isInSafezone(playerLoc)) { +// MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); +// return; +// } +// +// // Ignores the event if the player's ability is on cooldown. +// if (playerData.hasCooldown(abilityItem, true)) { +// return; +// } +// +// // TODO: Replace this with metadata. +// +// // Create a task to notify the player that the Life Steal ability has ended. +// BukkitTask lifeStealTask = new BukkitRunnable() { +// @Override +// public void run() { +// player.playSound(playerLoc, Sound.BAT_IDLE, 1, 1); +// MessageUtil.messagePlayer(player, "&cLife Steal is no longer active."); +// } +// }.runTaskLater(KitPvP.instance, Settings.vampireKitDuration * 20L); +// +// // Cancel the Life Steal cooldown task if it's active. +// TaskUtil.runTaskLater(() -> playerData.setLifeStealCooldown(null), Settings.vampireKitDuration * 20L); +// +// // Set the Life Steal cooldown task. +// playerData.setLifeStealCooldown(lifeStealTask); +// +// // Sets the player's ability cooldown. +// player.playSound(playerLoc, Sound.BAT_HURT, 1, 1); +// MessageUtil.messagePlayer(player, "&aLife Steal has been activated."); +// playerData.setCooldown(playerKit, abilityItem, Settings.vampireKitCooldown, true); +// } + +// /** +// * Handles the Life Steal ability. +// * +// * @param event The event. +// */ +// @EventHandler(ignoreCancelled = true) +// public static void onVampireHit(@NotNull EntityDamageByEntityEvent event) { +// // Ignores the event if the damager or target is not a player. +// if (!(event.getDamager() instanceof Player) +// || !(event.getEntity() instanceof Player)) { +// return; +// } +// +// // Player data +// Player player = (Player) event.getDamager(); +// PlayerData playerData = PlayerDataManager.getPlayerData(player); +// Location playerLoc = player.getLocation(); +// +// // Target data +// Player target = (Player) event.getEntity(); +// PlayerData targetData = PlayerDataManager.getPlayerData(target); +// Location targetLoc = target.getLocation(); +// +// // Ignores the event if the damager is not using the Vampire kit. +// if (!(playerData.getActiveKit() instanceof Vampire) +// || targetData.getActiveKit() == null +// || Regions.isInSafezone(playerLoc) +// || Regions.isInSafezone(targetLoc)) { +// return; +// } +// +// // Gives the player half a heart per hit. +// if (playerData.getLifeStealCooldown() != null) { +// double health = player.getHealth(); +// double maxHealth = player.getMaxHealth(); +// player.setHealth(Math.min(health + 1, maxHealth)); +// } +// } + /** * Handles the Vampire ability. * * @param event The event. */ @EventHandler - @SuppressWarnings("NestedMethodCall") public static void onVampireAbility(@NotNull PlayerInteractEvent event) { // Ignores the event if the player isn't right-clicking. if (event.getAction() != Action.RIGHT_CLICK_AIR @@ -63,70 +167,104 @@ public static void onVampireAbility(@NotNull PlayerInteractEvent event) { PlayerData playerData = PlayerDataManager.getPlayerData(player); Location playerLoc = player.getLocation(); Kit playerKit = playerData.getActiveKit(); - ItemStack item = event.getItem(); + ItemStack itemStack = event.getItem(); + Material abilityItem = Material.INK_SACK; - // Checks for common ability exclusions. - if (AbilityUtil.shouldBeExcluded(playerLoc, player, playerData, playerKit, item, Material.REDSTONE)) { + // Ignores the event if the given item does not match the desired item. + if (itemStack == null + || !itemStack.hasItemMeta() + || !itemStack.getItemMeta().hasDisplayName() + || itemStack.getType() != abilityItem) { return; } - // Create a task to notify the player that the Life Steal ability has ended. - BukkitTask lifeStealTask = new BukkitRunnable() { - @Override - public void run() { - player.playSound(playerLoc, Sound.BAT_IDLE, 1, 1); - MessageUtil.messagePlayer(player, "&cLife Steal is no longer active."); + // Ignores the event if the player is not using the desired kit. + if (!(playerKit instanceof Vampire)) { + return; + } + + // Ignores the event if the player is in a safe zone. + if (Regions.isInSafezone(playerLoc)) { + MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); + return; + } + + // Ignores the event if the player's ability is on cooldown. + if (playerData.hasCooldown(abilityItem, true)) { + return; + } + + // Gives the player Invisibility, Speed II, and Jump Boost III. + player.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, + Settings.vampireKitDuration * 20, 0, false, false)); + player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, + Settings.vampireKitDuration * 20, 1, false, false)); + player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, + Settings.vampireKitDuration * 20, 2, false, false)); + + // Plays a smoke sound effect. + player.getWorld().playEffect(playerLoc, Effect.LARGE_SMOKE, 1, 1); + + // Hides the player from other players. + for (Player target : Bukkit.getOnlinePlayers()) { + if (target == player) { + continue; + } + + target.hidePlayer(player); + } + + // Create a task that restores the player's visibility. + int changeCount = playerData.getChangeCount(); + TaskUtil.runTaskLater(() -> { + if (playerData.getChangeCount() == changeCount) { + player.getWorld().playSound(playerLoc, Sound.BAT_IDLE, 1, 1); + player.removePotionEffect(PotionEffectType.INVISIBILITY); + player.removePotionEffect(PotionEffectType.SPEED); + player.removePotionEffect(PotionEffectType.JUMP); + MessageUtil.messagePlayer(player, "&cYou are no longer invisible."); } - }.runTaskLater(KitPvP.instance, Settings.vampireKitDuration * 20L); - // Cancel the Life Steal cooldown task if it's active. - TaskUtil.runTaskLater(() -> playerData.setLifeStealCooldown(null), Settings.vampireKitDuration * 20L); + for (Player target : Bukkit.getOnlinePlayers()) { + if (target == player) { + continue; + } - // Set the Life Steal cooldown task. - playerData.setLifeStealCooldown(lifeStealTask); + target.showPlayer(player); + } + }, Settings.vampireKitDuration * 20L + 1L); // Sets the player's ability cooldown. - player.playSound(playerLoc, Sound.BAT_HURT, 1, 1); - MessageUtil.messagePlayer(player, "&aLife Steal has been activated."); - playerData.setCooldown(playerKit, Settings.vampireKitCooldown, true); + playerData.setNoFall(true); + player.getWorld().playSound(playerLoc, Sound.BAT_DEATH, 1, 1); + MessageUtil.messagePlayer(player, "&aYou are now invisible."); + playerData.setCooldown(playerKit, abilityItem, Settings.vampireKitCooldown, true); } /** - * Handles the Life Steal ability. + * Handles damaging players while invisible. * - * @param event The event. + * @param event EntityDamageByEntityEvent */ @EventHandler(ignoreCancelled = true) - public static void onVampireHit(@NotNull EntityDamageByEntityEvent event) { - // Ignores the event if the damager or target is not a player. - if (!(event.getDamager() instanceof Player) - || !(event.getEntity() instanceof Player)) { - return; - } - - // Player data - Player player = (Player) event.getDamager(); - PlayerData playerData = PlayerDataManager.getPlayerData(player); - Location playerLoc = player.getLocation(); - - // Target data - Player target = (Player) event.getEntity(); - PlayerData targetData = PlayerDataManager.getPlayerData(target); - Location targetLoc = target.getLocation(); + public static void onInvisHit(@NotNull EntityDamageByEntityEvent event) { + Entity damagerEntity = event.getDamager(); + Entity targetEntity = event.getEntity(); - // Ignores the event if the damager is not using the Vampire kit. - if (!(playerData.getActiveKit() instanceof Vampire) - || targetData.getActiveKit() == null - || Regions.isInSafezone(playerLoc) - || Regions.isInSafezone(targetLoc)) { + // Checks if the entities are both players. + if (!(damagerEntity instanceof Player) + || !(targetEntity instanceof Player)) { return; } - // Gives the player half a heart per hit. - if (playerData.getLifeStealCooldown() != null) { - double health = player.getHealth(); - double maxHealth = player.getMaxHealth(); - player.setHealth(Math.min(health + 1, maxHealth)); + Player damager = (Player) damagerEntity; + PlayerData damagerData = PlayerDataManager.getPlayerData(damager); + + // Cancels hits while the player is invisible. + if (damagerData.getActiveKit() instanceof Vampire + && damager.hasPotionEffect(PotionEffectType.INVISIBILITY)) { + MessageUtil.messagePlayer(damager, "&cYou can't damage other players while invisible."); + event.setCancelled(true); } } } diff --git a/src/main/java/net/foulest/kitpvp/region/Spawn.java b/src/main/java/net/foulest/kitpvp/region/Spawn.java index bde7812..6029d21 100644 --- a/src/main/java/net/foulest/kitpvp/region/Spawn.java +++ b/src/main/java/net/foulest/kitpvp/region/Spawn.java @@ -19,9 +19,11 @@ import lombok.Data; import lombok.Getter; +import net.foulest.kitpvp.KitPvP; import net.foulest.kitpvp.combattag.CombatTag; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; +import net.foulest.kitpvp.listeners.kits.ReaperListener; import net.foulest.kitpvp.util.MessageUtil; import net.foulest.kitpvp.util.Settings; import org.bukkit.Bukkit; @@ -82,6 +84,13 @@ public static void teleport(Player player) { CombatTag.remove(player); playerData.setActiveKit(null); + // Removes the Reaper Mark if the player has one. + ReaperListener.removeReaperMark(playerData, false, false); + + // Removes the player's Soldier data. + playerData.setSoldierRage(0.0); + player.removeMetadata("buffBanner", KitPvP.getInstance()); + for (PotionEffect effect : player.getActivePotionEffects()) { PotionEffectType effectType = effect.getType(); player.removePotionEffect(effectType); @@ -89,8 +98,13 @@ public static void teleport(Player player) { playerData.giveDefaultItems(); + player.setMaxHealth(20); player.setHealth(20); player.teleport(location); + + // Update the player's kit change count. + int changeCount = playerData.getChangeCount(); + playerData.setChangeCount(changeCount + 1); } /** diff --git a/src/main/java/net/foulest/kitpvp/util/AbilityUtil.java b/src/main/java/net/foulest/kitpvp/util/AbilityUtil.java index a970b32..b5252d1 100644 --- a/src/main/java/net/foulest/kitpvp/util/AbilityUtil.java +++ b/src/main/java/net/foulest/kitpvp/util/AbilityUtil.java @@ -20,14 +20,11 @@ import lombok.Data; import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; -import net.foulest.kitpvp.kits.Kit; -import net.foulest.kitpvp.kits.type.Ninja; +import net.foulest.kitpvp.kits.type.Vampire; import net.foulest.kitpvp.region.Regions; import org.bukkit.Location; -import org.bukkit.Material; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; @@ -63,7 +60,7 @@ public class AbilityUtil { if (targetData.getActiveKit() == null || Regions.isInSafezone(targetLoc) || (target.hasPotionEffect(PotionEffectType.INVISIBILITY) - && targetData.getActiveKit() instanceof Ninja)) { + && targetData.getActiveKit() instanceof Vampire)) { continue; } @@ -71,63 +68,4 @@ public class AbilityUtil { } return nearbyPlayers; } - - /** - * Checks for exclusions before executing an ability. - * - * @param playerLoc The player's location. - * @param player The player. - * @param playerData The player's data. - * @return Whether the ability should be ignored. - */ - public static boolean shouldBeExcluded(Location playerLoc, Player player, - @NotNull PlayerData playerData, @NotNull Kit desiredKit) { - // Ignores the event if the player is not using the desired kit. - if (playerData.getActiveKit().getClass() != desiredKit.getClass()) { - return true; - } - - // Ignores the event if the player is in a safe zone. - if (Regions.isInSafezone(playerLoc)) { - MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); - return true; - } - - // Ignores the event if the player's ability is on cooldown. - return playerData.hasCooldown(true); - } - - /** - * Checks for exclusions before executing an ability. - * - * @param playerLoc The player's location. - * @param player The player. - * @param playerData The player's data. - * @return Whether the ability should be ignored. - */ - public static boolean shouldBeExcluded(Location playerLoc, Player player, - @NotNull PlayerData playerData, @NotNull Kit desiredKit, - ItemStack itemStack, @NotNull Material itemType) { - // Ignores the event if the given item does not match the desired item. - if (itemStack == null - || !itemStack.hasItemMeta() - || !itemStack.getItemMeta().hasDisplayName() - || itemStack.getType() != itemType) { - return true; - } - - // Ignores the event if the player is not using the desired kit. - if (playerData.getActiveKit().getClass() != desiredKit.getClass()) { - return true; - } - - // Ignores the event if the player is in a safe zone. - if (Regions.isInSafezone(playerLoc)) { - MessageUtil.messagePlayer(player, ConstantUtil.ABILITY_IN_SPAWN); - return true; - } - - // Ignores the event if the player's ability is on cooldown. - return playerData.hasCooldown(true); - } } diff --git a/src/main/java/net/foulest/kitpvp/util/BlockUtil.java b/src/main/java/net/foulest/kitpvp/util/BlockUtil.java deleted file mode 100644 index b105164..0000000 --- a/src/main/java/net/foulest/kitpvp/util/BlockUtil.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * KitPvP - a fully-featured core plugin for the KitPvP gamemode. - * Copyright (C) 2024 Foulest (https://github.com/Foulest) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.foulest.kitpvp.util; - -import lombok.Data; -import net.foulest.kitpvp.util.data.ConcurrentStream; -import net.foulest.kitpvp.util.raytrace.BoundingBox; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Utility class for block-related methods. - * - * @author Foulest - */ -@Data -public class BlockUtil { - - /** - * Checks if a player is in an unloaded chunk. - * - * @param player The player to check. - * @return Whether the player is in an unloaded chunk. - */ - private static boolean isPlayerInUnloadedChunk(@NotNull Player player) { - Location location = player.getLocation(); - int blockX = location.getBlockX(); - int blockZ = location.getBlockZ(); - return !location.getWorld().isChunkLoaded(blockX >> 4, blockZ >> 4); - } - - /** - * Gets the blocks that a player is colliding with. - * - * @param player The player to check. - * @param boundingBox The bounding box to check. - * @return The blocks that the player is colliding with. - */ - @Contract("_, _ -> new") - private static @NotNull ConcurrentStream getCollidingBlocks(Player player, @NotNull BoundingBox boundingBox) { - List collidingBlocks = boundingBox.getCollidingBlocks(player); - return new ConcurrentStream<>(collidingBlocks, false); - } - - /** - * Checks if a player is colliding with a solid block. - * - * @param player The player to check. - * @return Whether the player is colliding with a solid block. - */ - private static boolean collidesWithSolid(Player player, BoundingBox boundingBox) { - ConcurrentStream collidingBlocks = getCollidingBlocks(player, boundingBox); - - return collidingBlocks.any(block -> { - Material type = block.getType(); - - return type.isSolid() - || type == Material.WATER_LILY - || type == Material.FLOWER_POT - || type == Material.CARPET - || type == Material.SNOW - || type == Material.SKULL; - }); - } - - /** - * Checks if a player is on the ground. - * - * @param player The player to check. - * @param offset The offset to check. - * @return Whether the player is on the ground. - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public static boolean isOnGroundOffset(Player player, double offset) { - if (isPlayerInUnloadedChunk(player)) { - return true; - } - - BoundingBox boundingBox = new BoundingBox(player).expand(0.0, 0.0, 0.0) - .expandMin(0.0, offset, 0.0) - .expandMax(0.0, -1.0, 0.0); - return collidesWithSolid(player, boundingBox); - } -} diff --git a/src/main/java/net/foulest/kitpvp/util/PlaceholderUtil.java b/src/main/java/net/foulest/kitpvp/util/PlaceholderUtil.java index ec10c51..29eda0e 100644 --- a/src/main/java/net/foulest/kitpvp/util/PlaceholderUtil.java +++ b/src/main/java/net/foulest/kitpvp/util/PlaceholderUtil.java @@ -22,6 +22,7 @@ import net.foulest.kitpvp.data.PlayerData; import net.foulest.kitpvp.data.PlayerDataManager; import net.foulest.kitpvp.kits.Kit; +import net.foulest.kitpvp.kits.type.Soldier; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -50,6 +51,7 @@ public class PlaceholderUtil extends PlaceholderExpansion { @Override public String onPlaceholderRequest(Player player, @NotNull String identifier) { PlayerData playerData = PlayerDataManager.getPlayerData(player); + Kit activeKit = playerData.getActiveKit(); int bounty = playerData.getBounty(); StringBuilder builder = new StringBuilder(); @@ -107,8 +109,6 @@ public String onPlaceholderRequest(Player player, @NotNull String identifier) { break; case "activekit": - Kit activeKit = playerData.getActiveKit(); - if (activeKit == null) { builder.append("None"); break; @@ -126,6 +126,16 @@ public String onPlaceholderRequest(Player player, @NotNull String identifier) { builder.append(bounty == 0 ? "" : "&6Bounty: &e&l$" + bounty); break; + case "soldier_rage": + if (!(activeKit instanceof Soldier)) { + builder.append("None"); + break; + } + + int percentage = (int) ((playerData.getSoldierRage() / Settings.soldierKitMaxRage) * 100); + builder.append(percentage).append("%"); + break; + default: break; } diff --git a/src/main/java/net/foulest/kitpvp/util/Settings.java b/src/main/java/net/foulest/kitpvp/util/Settings.java index a3e141e..d8565a5 100644 --- a/src/main/java/net/foulest/kitpvp/util/Settings.java +++ b/src/main/java/net/foulest/kitpvp/util/Settings.java @@ -102,8 +102,14 @@ public class Settings { // Fisherman kit settings public static int fishermanKitCooldown; + public static int fishermanKitRodCooldown; public static int fishermanKitDuration; + // Jester kit settings + public static int jesterKitCooldown; + public static int jesterKitDuration; + public static double jesterKitDamage; + // Kangaroo kit settings public static int kangarooKitCooldown; @@ -120,6 +126,15 @@ public class Settings { public static int pyroKitDuration; public static double pyroKitDamage; + // Reaper kit settings + public static int reaperKitCooldown; + public static int reaperKitDuration; + + // Soldier kit settings + public static int soldierKitBattalionDuration; + public static int soldierKitBannerDuration; + public static int soldierKitMaxRage; + // Tank kit settings public static int tankKitCooldown; public static int tankKitDuration; @@ -258,9 +273,15 @@ public static void loadConfigValues() { archerKitDuration = config.getInt("kitpvp.kits.archer.ability.duration"); // Fisherman kit settings + fishermanKitRodCooldown = config.getInt("kitpvp.kits.fisherman.rod-cooldown"); fishermanKitCooldown = config.getInt("kitpvp.kits.fisherman.ability.cooldown"); fishermanKitDuration = config.getInt("kitpvp.kits.fisherman.ability.duration"); + // Jester kit settings + jesterKitCooldown = config.getInt("kitpvp.kits.jester.ability.cooldown"); + jesterKitDuration = config.getInt("kitpvp.kits.jester.ability.duration"); + jesterKitDamage = config.getDouble("kitpvp.kits.jester.ability.damage"); + // Kangaroo kit settings kangarooKitCooldown = config.getInt("kitpvp.kits.kangaroo.ability.cooldown"); @@ -277,6 +298,15 @@ public static void loadConfigValues() { pyroKitDuration = config.getInt("kitpvp.kits.pyro.ability.duration"); pyroKitDamage = config.getDouble("kitpvp.kits.pyro.ability.damage"); + // Reaper kit settings + reaperKitCooldown = config.getInt("kitpvp.kits.reaper.ability.cooldown"); + reaperKitDuration = config.getInt("kitpvp.kits.reaper.ability.duration"); + + // Soldier kit settings + soldierKitBattalionDuration = config.getInt("kitpvp.kits.soldier.ability.battalion-duration"); + soldierKitBannerDuration = config.getInt("kitpvp.kits.soldier.ability.banner-duration"); + soldierKitMaxRage = config.getInt("kitpvp.kits.soldier.ability.max-rage"); + // Tank kit settings tankKitCooldown = config.getInt("kitpvp.kits.tank.ability.cooldown"); tankKitDuration = config.getInt("kitpvp.kits.tank.ability.duration"); diff --git a/src/main/java/net/foulest/kitpvp/util/raytrace/BoundingBox.java b/src/main/java/net/foulest/kitpvp/util/raytrace/BoundingBox.java index 345316f..45988c3 100644 --- a/src/main/java/net/foulest/kitpvp/util/raytrace/BoundingBox.java +++ b/src/main/java/net/foulest/kitpvp/util/raytrace/BoundingBox.java @@ -24,6 +24,7 @@ import net.minecraft.server.v1_8_R3.IBlockData; import net.minecraft.server.v1_8_R3.WorldServer; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; diff --git a/src/main/java/net/foulest/kitpvp/util/yaml/CustomYamlConfiguration.java b/src/main/java/net/foulest/kitpvp/util/yaml/CustomYamlConfiguration.java index 48d7c4e..a5de871 100644 --- a/src/main/java/net/foulest/kitpvp/util/yaml/CustomYamlConfiguration.java +++ b/src/main/java/net/foulest/kitpvp/util/yaml/CustomYamlConfiguration.java @@ -209,13 +209,12 @@ public void load(Reader reader) throws IOException, InvalidConfigurationExceptio * @param contents The YAML data to parse and store comments for. */ private void parseAndStoreComments(@NotNull String contents) { - String[] lines = contents.split("\n"); - StringBuilder commentBuilder = new StringBuilder(); - String lastComment = commentBuilder.toString(); + String @NotNull [] lines = contents.split("\n"); + @NotNull StringBuilder commentBuilder = new StringBuilder(); boolean isHeader = true; // Assume the first comments are header comments // Define the pattern within this method - Pattern keyPattern = Pattern.compile("^\\s*([\\w\\-]+):.*"); + @NotNull Pattern keyPattern = Pattern.compile("^\\s*([\\w\\-]+):.*"); for (String entry : lines) { String line = entry; @@ -235,17 +234,19 @@ private void parseAndStoreComments(@NotNull String contents) { } else { if (!line.trim().isEmpty() && isHeader) { // Store header comments and mark that we've found a non-comment line + @NotNull String lastComment = commentBuilder.toString(); commentsMap.put("__header__", lastComment); commentBuilder.setLength(0); isHeader = false; // No longer reading header comments } - Matcher matcher = keyPattern.matcher(line); + @NotNull Matcher matcher = keyPattern.matcher(line); if (matcher.find()) { // Found a key, store the accumulated comments if any String key = matcher.group(1); if (commentBuilder.length() > 0) { + @NotNull String lastComment = commentBuilder.toString(); commentsMap.put(key, lastComment); commentBuilder.setLength(0); // Reset comment builder } @@ -255,6 +256,7 @@ private void parseAndStoreComments(@NotNull String contents) { // In case the file ends with comments not associated with a key if (commentBuilder.length() > 0 && !isHeader) { + @NotNull String lastComment = commentBuilder.toString(); commentsMap.put("__footer__", lastComment); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 68e3c11..d330971 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -16,7 +16,7 @@ kitpvp: x: 0.5 y: 64.0 z: 0.5 - yaw: 0.0 + yaw: 90.0 pitch: 0.0 # ----------------------------------------------- # Combat Tag Settings @@ -110,299 +110,355 @@ kitpvp: ability: cooldown: 30 duration: 5 - display-item: "BOW" + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.archer" + name: kitpvp.kit.archer default: true lore: - - "&7Style: &aOffensive" - - "" - - "&7Master of long-ranged combat." - effects: [ ] + - '&7Style: &aOffensive' + - '' + - '&7Master of long-ranged combat.' + effects: [] items: - - material: "WOOD_SWORD" - name: "&aArcher's Sword" + - material: WOOD_SWORD + name: '&aArcher''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &c-25% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &c-25% damage penalty' unbreakable: true hide-info: true - - material: "BOW" - name: "&aArcher's Bow" + - material: BOW + name: '&aArcher''s Bow' lore: - - "&7Compared to Bow:" - - "&8\u2503 &7No notable changes." + - '&7Compared to Bow:' + - '&8┃ &7No notable changes.' unbreakable: true hide-info: true - - material: "FEATHER" - name: "&aSpeed Boost &7(Right Click)" + - material: FEATHER + name: '&aSpeed Boost &7(Right Click)' lore: - - "&7Gain a temporary speed boost." + - '&7Gain a temporary speed boost.' unbreakable: true hide-info: true amount: 1 slot: 2 - - material: "ARROW" + - material: ARROW unbreakable: true hide-info: true amount: 32 slot: 8 armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTMwMzIyZDM1NjgzMjI4ZjMwZmJjYThjZDFjMmE2MDIwODczMDE1MTZmNmI0MzhiNDhkNjc2ZWU1NTIwNzU3MCJ9fX0=" - name: "&fArcher's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTMwMzIyZDM1NjgzMjI4ZjMwZmJjYThjZDFjMmE2MDIwODczMDE1MTZmNmI0MzhiNDhkNjc2ZWU1NTIwNzU3MCJ9fX0= + name: '&fArcher''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fArcher's Chestplate" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fArcher''s Chestplate' + lore: [] unbreakable: true hide-info: true leggings: - material: "LEATHER_LEGGINGS" - name: "&fArcher's Leggings" - lore: [ ] + material: LEATHER_LEGGINGS + name: '&fArcher''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "LEATHER_BOOTS" - name: "&fArcher's Boots" - lore: [ ] + material: LEATHER_BOOTS + name: '&fArcher''s Boots' + lore: [] unbreakable: true hide-info: true fisherman: enabled: true cost: 0 + rod-cooldown: 2 ability: cooldown: 30 - duration: 3 - display-item: "FISHING_ROD" + duration: 2 + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.fisherman" + name: kitpvp.kit.fisherman default: true lore: - - "&7Style: &aMixed" - - "" - - "&7Hooks players to your location." + - '&7Style: &aMixed' + - '' + - '&7Hooks players to your location.' effects: - - type: "WATER_BREATHING" + - type: WATER_BREATHING items: - - material: "STONE_SWORD" - name: "&aFisherman's Sword" + - material: WOOD_SWORD + name: '&aFisherman''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &7No notable changes." + - '&7Compared to Stone Sword:' + - '&8┃ &b+50% damage on wet players' + - '&8┃ &c-25% damage penalty' unbreakable: true hide-info: true - - material: "FISHING_ROD" - name: "&aHookshot &7(Right Click)" + - material: FISHING_ROD + name: '&aHookshot &7(Right Click)' lore: - - "&7Hooks players to your location." + - '&7Hooks players to your location.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTcxNTI4NzZiYzNhOTZkZDJhMjI5OTI0NWVkYjNiZWVmNjQ3YzhhNTZhYzg4NTNhNjg3YzNlN2I1ZDhiYiJ9fX0=" - name: "&fFisherman's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTcxNTI4NzZiYzNhOTZkZDJhMjI5OTI0NWVkYjNiZWVmNjQ3YzhhNTZhYzg4NTNhNjg3YzNlN2I1ZDhiYiJ9fX0= + name: '&fFisherman''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fFisherman's Chestplate" - color: "BF8426" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fFisherman''s Chestplate' + color: BF8426 + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fFisherman's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fFisherman''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fFisherman's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fFisherman''s Boots' + lore: [] unbreakable: true hide-info: true kangaroo: enabled: true cost: 0 ability: - cooldown: 15 - display-item: "FIREWORK" + cooldown: 5 + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.kangaroo" + name: kitpvp.kit.kangaroo default: true lore: - - "&7Style: &aMixed" - - "" - - "&7Hooks players to your location." + - '&7Style: &aMixed' + - '' + - '&7Hop around like a Kangaroo.' effects: - - type: "JUMP" + - type: JUMP amplifier: 1 + - type: SPEED + amplifier: 0 items: - - material: "WOOD_SWORD" - name: "&aKangaroo's Sword" + - material: IRON_SPADE + name: '&aMarket Gardener' + lore: + - '&7Compared to Stone Sword:' + - '&8┃ &b+150% damage bonus while hopping' + - '&8┃ &c-25% damage penalty' + unbreakable: true + hide-info: true + - material: FIREWORK + name: '&aHop &7(Right Click)' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &c-25% damage penalty" + - '&7Hop around like a Kangaroo.' + - '&8┃ &7Sneak in the air to hop forward.' + unbreakable: true + hide-info: true + armor: + helmet: + material: SKULL_ITEM + base64: ewogICJ0aW1lc3RhbXAiIDogMTYyMjI4MzIxMTIwOSwKICAicHJvZmlsZUlkIiA6ICIzOTg5OGFiODFmMjU0NmQxOGIyY2ExMTE1MDRkZGU1MCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNeVV1aWRJcyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kM2I2MWVjNGI1MjU2NDUzZWRjOTU0MTZhODJiNTRkMjQyMzdhZTgxNGQzNjYzMjQ1MzZhZTkxYzgxYzM5NWVlIgogICAgfQogIH0KfQ== + name: '&fKangaroo''s Head' + lore: [] + unbreakable: true + hide-info: true + chestplate: + material: LEATHER_CHESTPLATE + name: '&fKangaroo''s Chestplate' + color: '6E5927' + lore: [] unbreakable: true hide-info: true - - material: "STONE_SPADE" - name: "&aMarket Gardener" + leggings: + material: IRON_LEGGINGS + name: '&fKangaroo''s Leggings' + lore: [] + unbreakable: true + hide-info: true + boots: + material: IRON_BOOTS + name: '&fKangaroo''s Boots' + lore: [] + unbreakable: true + hide-info: true + jester: + enabled: true + cost: 0 + ability: + cooldown: 15 + damage: 2.0 + duration: 2 + display-item: SKULL_ITEM + permission: + name: kitpvp.kit.jester + default: true + lore: + - '&7Style: &aOffensive' + - '' + - '&7Turns deadly under pressure.' + effects: [] + items: + - material: STONE_SWORD + name: '&aJester''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &b+150% damage bonus while hopping" - - "&8\u2503 &c-40% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &b+15% damage when below 50% health' + - '&8┃ &c-15% damage when above 50% health' unbreakable: true hide-info: true - - material: "FIREWORK" - name: "&aHop &7(Right Click)" + - material: STICK + name: '&aWrap Assassin &7(Right Click)' lore: - - "&7Hop around like a Kangaroo." + - '&7Launches an ornament that bleeds on impact.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "ewogICJ0aW1lc3RhbXAiIDogMTYyMjI4MzIxMTIwOSwKICAicHJvZmlsZUlkIiA6ICIzOTg5OGFiODFmMjU0NmQxOGIyY2ExMTE1MDRkZGU1MCIsCiAgInByb2ZpbGVOYW1lIiA6ICJNeVV1aWRJcyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9kM2I2MWVjNGI1MjU2NDUzZWRjOTU0MTZhODJiNTRkMjQyMzdhZTgxNGQzNjYzMjQ1MzZhZTkxYzgxYzM5NWVlIgogICAgfQogIH0KfQ==" - name: "&fKangaroo's Head" - lore: [ ] + material: SKULL_ITEM + base64: ewogICJ0aW1lc3RhbXAiIDogMTcxMTgzNjc3MTUzOSwKICAicHJvZmlsZUlkIiA6ICJmNTBjOGRkN2FiN2Y0ZmUyYWI4ZGI1M2NjYzRiYWQxMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJtYWNoYWRvVF9UIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzNiOTAyMmM4NjU1NjRhNmMyNDAxMmU0MDVhZjljZjc3OWMzMTQwZWU4ZDAzYTBjZjI1NjBlMTQ3M2QxNWJhYTMiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ== + name: '&fJester''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fKangaroo's Chestplate" - color: "6E5927" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fJester''s Chestplate' + color: 9F4746 + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fKangaroo's Leggings" - lore: [ ] + material: CHAINMAIL_LEGGINGS + name: '&fJester''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fKangaroo's Boots" - lore: [ ] + material: CHAINMAIL_BOOTS + name: '&fJester''s Boots' + lore: [] unbreakable: true hide-info: true knight: enabled: true cost: 0 - display-item: "IRON_CHESTPLATE" + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.knight" + name: kitpvp.kit.knight default: true lore: - - "&7Style: &aMixed" - - "" - - "&7No perks or abilities." - effects: [ ] + - '&7Style: &aMixed' + - '' + - '&7No perks or abilities.' + effects: [] items: - - material: "STONE_SWORD" - name: "&aKnight's Sword" + - material: STONE_SWORD + name: '&aKnight''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &7No notable changes." + - '&7Compared to Stone Sword:' + - '&8┃ &7No notable changes.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODA1Mzk3M2U3YzUyMzcyYzNiMTExMzk0ZGZmOTUxOWNiYWMxZmJhM2Y2NTliMjE4NmJlZjhlZWY5ZTEwZmEyIn19fQ==" - name: "&fKnight's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODA1Mzk3M2U3YzUyMzcyYzNiMTExMzk0ZGZmOTUxOWNiYWMxZmJhM2Y2NTliMjE4NmJlZjhlZWY5ZTEwZmEyIn19fQ== + name: '&fKnight''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "IRON_CHESTPLATE" - name: "&fKnight's Chestplate" - lore: [ ] + material: IRON_CHESTPLATE + name: '&fKnight''s Chestplate' + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fKnight's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fKnight''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fKnight's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fKnight''s Boots' + lore: [] unbreakable: true hide-info: true mage: enabled: true cost: 0 ability: - cooldown: 30 - duration: 5 - display-item: "GLOWSTONE_DUST" + cooldown: 20 + duration: 3 + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.mage" + name: kitpvp.kit.mage default: true lore: - - "&7Style: &aMixed" - - "" - - "&7Applies debuffs to players." - effects: [ ] + - '&7Style: &aMixed' + - '' + - '&7Applies debuffs to players.' + effects: [] items: - - material: "STONE_SWORD" - name: "&aMage's Sword" + - material: STONE_SWORD + name: '&aMage''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &7No notable changes." + - '&7Compared to Stone Sword:' + - '&8┃ &7No notable changes.' unbreakable: true hide-info: true - - material: "BLAZE_ROD" - name: "&aSun Staff" + - material: BLAZE_ROD + name: '&aSun Staff' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &b+100% damage against burning players" - - "&8\u2503 &b+25% fire damage resistance while active" - - "&8\u2503 &c-40% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &b+100% damage against burning players' + - '&8┃ &b+25% fire damage resistance while active' + - '&8┃ &c-40% damage penalty' unbreakable: true hide-info: true - - material: "GLOWSTONE_DUST" - name: "&aStasis &7(Right Click)" + - material: GLOWSTONE_DUST + name: '&aStasis &7(Right Click)' lore: - - "&7Applies debuffs to players." + - '&7Applies debuffs to players.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmVlMmIxNTQ4NTQ1ZTJhMjQ5N2JkMjRhYWM3OTE3OTI2NTRlZjU4N2E1YWI3M2QzNmFiN2Y1ZDliZjcyYTU0NyJ9fX0=" - name: "&fMage's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmVlMmIxNTQ4NTQ1ZTJhMjQ5N2JkMjRhYWM3OTE3OTI2NTRlZjU4N2E1YWI3M2QzNmFiN2Y1ZDliZjcyYTU0NyJ9fX0= + name: '&fMage''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fMage's Chestplate" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fMage''s Chestplate' + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fMage's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fMage''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fMage's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fMage''s Boots' + lore: [] unbreakable: true hide-info: true ninja: @@ -411,127 +467,253 @@ kitpvp: ability: cooldown: 30 duration: 5 - display-item: "NETHER_STAR" + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.ninja" + name: kitpvp.kit.ninja default: true lore: - - "&7Style: &aOffensive" - - "" - - "&7An agile, stealthy class." + - '&7Style: &aOffensive' + - '' + - '&7An agile, stealthy class.' effects: - - type: "SPEED" + - type: SPEED amplifier: 1 items: - - material: "GOLD_SWORD" - name: "&aNinja's Blade" + - material: GOLD_SWORD + name: '&aNinja''s Blade' + lore: + - '&7Compared to Stone Sword:' + - '&8┃ &b+50% damage when behind target' + - '&8┃ &c-25% damage penalty' + unbreakable: true + hide-info: true + armor: + helmet: + material: SKULL_ITEM + base64: ewogICJ0aW1lc3RhbXAiIDogMTY4MjcxNTU5NzYyNSwKICAicHJvZmlsZUlkIiA6ICJjNDdiNWNmNDBkNTU0MWNjYjFiNTE1ZjRiNjA3ZWQzOSIsCiAgInByb2ZpbGVOYW1lIiA6ICJ0YXZvb29oIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzg2MjA0ZjNjZTE4YTM2NWU5Yzc0MTMxZjFjYjVhYzYyMGExYmIzMzBkMTRlOGI0NGQwMTEzZTg4NjZjMmU0MTYiCiAgICB9CiAgfQp9 + name: '&fNinja''s Head' + lore: [] + unbreakable: true + hide-info: true + chestplate: + material: LEATHER_CHESTPLATE + name: '&fNinja''s Chestplate' + color: 242131 + lore: [] + unbreakable: true + hide-info: true + leggings: + material: CHAINMAIL_LEGGINGS + name: '&fNinja''s Leggings' + lore: [] + unbreakable: true + hide-info: true + boots: + material: CHAINMAIL_BOOTS + name: '&fNinja''s Boots' + lore: [] + unbreakable: true + hide-info: true + pyro: + enabled: true + cost: 0 + ability: + cooldown: 15 + duration: 3 + damage: 4.0 + display-item: SKULL_ITEM + permission: + name: kitpvp.kit.pyro + default: true + lore: + - '&7Style: &aOffensive' + - '' + - '&7Set other players ablaze.' + effects: + - type: FIRE_RESISTANCE + items: + - material: STONE_AXE + name: '&aAxtinguisher' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &b+50% damage when behind target" - - "&8\u2503 &c-25% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &b+50% damage to burning players and extinguishes them' + - '&8┃ &bKilling blows on burning players grant a speed boost' + - '&8┃ &c-25% damage penalty' unbreakable: true hide-info: true - - material: "INK_SACK" - name: "&aShadow Sneak &7(Right Click)" + - material: GOLD_AXE + name: '&aPowerjack' lore: - - "&7Temporarily vanish from sight." - durability: 8 + - '&7Compared to Stone Sword:' + - '&8┃ &bRestores 3 hearts on kill' + - '&8┃ &b+20% movement speed when active' + - '&8┃ &c+20% damage vulnerability when active' + - '&8┃ &c-40% damage penalty' + unbreakable: true + hide-info: true + - material: FLINT_AND_STEEL + name: '&aFlare Gun &7(Right Click)' + lore: + - '&7Shoots a flare that ignites players.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjQ2ZmZlNGY2OGRhYWEwZjgzNDUzNmNiNTM4NmEzYTc5ZTZiM2U4NDM1OTY5NDM4MDRlMWIwOGE4MmVkNDRhNiJ9fX0=" - name: "&fNinja's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWFmNTc2NDU0Y2I2NDFhNmU1OTVlZGY0ZTc3YTcwYzIwM2U4OGVjYWIwZjIyMGQzZmUzMGZiM2NjYzhjOGJhOCJ9fX0= + name: '&fPyro''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fNinja's Chestplate" - color: "0C0C0C" - lore: [ ] + material: GOLD_CHESTPLATE + name: '&fPyro''s Chestplate' + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fNinja's Leggings" - lore: [ ] + material: CHAINMAIL_LEGGINGS + name: '&fPyro''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fNinja's Boots" - lore: [ ] + material: CHAINMAIL_BOOTS + name: '&fPyro''s Boots' + lore: [] unbreakable: true hide-info: true - pyro: + reaper: enabled: true cost: 0 ability: - cooldown: 30 - duration: 5 - damage: 5.0 - display-item: "FLINT_AND_STEEL" + duration: 15 + cooldown: 15 + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.pyro" + name: kitpvp.kit.reaper default: true lore: - - "&7Style: &aOffensive" - - "" - - "&7Set other players ablaze." - effects: - - type: "FIRE_RESISTANCE" + - '&7Style: &aOffensive' + - '' + - '&7Marks enemies for the inevitable.' + effects: [] + items: + - material: IRON_HOE + name: '&aReaper''s Scythe' + lore: + - '&7Compared to Stone Sword:' + - '&8┃ &bReaper Mark is applied on hit' + - '&8┃ &b+50% damage against marked targets' + - '&8┃ &cOnly one mark can be active at a time' + - '&8┃ &cMarks expire after 15 seconds or on death' + - '&8┃ &c-25% damage penalty' + unbreakable: true + hide-info: true + - material: PAPER + name: '&aClear Marks &7(Right Click)' + lore: + - '&7Clears all active marks.' + unbreakable: true + hide-info: true + armor: + helmet: + material: SKULL_ITEM + base64: ewogICJ0aW1lc3RhbXAiIDogMTcyMDA3ODU4MjgzOCwKICAicHJvZmlsZUlkIiA6ICJhMzdlOGYyM2MyMTk0NjJiYjY3ZWUxY2I5OTM2YWY2NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJEb29yYWxleSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS8zNjhlOTM1MzI2MGU4N2JjYzkyZjhiMjdiYmRhMmY2Y2FiMWYwNDc2MWQ4YzU5Y2QzZTFjYzg4YzhmYjU0NzRjIiwKICAgICAgIm1ldGFkYXRhIiA6IHsKICAgICAgICAibW9kZWwiIDogInNsaW0iCiAgICAgIH0KICAgIH0KICB9Cn0= + name: '&fReaper''s Head' + lore: [] + unbreakable: true + hide-info: true + chestplate: + material: LEATHER_CHESTPLATE + name: '&fReaper''s Chestplate' + color: 0 + lore: [] + unbreakable: true + hide-info: true + leggings: + material: LEATHER_LEGGINGS + name: '&fReaper''s Leggings' + color: 0 + lore: [] + unbreakable: true + hide-info: true + boots: + material: LEATHER_BOOTS + name: '&fReaper''s Boots' + color: 0 + lore: [] + unbreakable: true + hide-info: true + soldier: + enabled: true + cost: 0 + ability: + battalion-duration: 5 + banner-duration: 5 + max-rage: 80 + display-item: SKULL_ITEM + permission: + name: kitpvp.kit.soldier + default: true + lore: + - '&7Style: &aDefensive' + - '' + - '&7Build up your rage.' + effects: [] items: - - material: "STONE_AXE" - name: "&aAxtinguisher" + - material: WOOD_SWORD + name: '&aSoldier''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &bMini-crits burning players and extinguishes them" - - "&8\u2503 &bKilling blows on burning players grant a speed boost" - - "&8\u2503 &c-25% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &bApplies speed boost on hit' + - '&8┃ &bSpeed boost applies to teammates hit' + - '&8┃ &c-25% damage penalty' unbreakable: true hide-info: true - - material: "GOLD_AXE" - name: "&aPowerjack" + - material: POWERED_MINECART + name: '&aBattalion''s Backup &7(Right Click)' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &bRestores 3 hearts on kill" - - "&8\u2503 &b+20% movement speed when active" - - "&8\u2503 &c+20% damage vulnerability when active" - - "&8\u2503 &c-40% damage penalty" + - '&7Provides nearby teammates with:' + - '&8┃ &bAbsorption and Resistance' + - '&8┃ &cEffect lasts for 5 seconds' + - '&8┃ &cCharge gained with damage dealt' unbreakable: true hide-info: true - - material: "FIREBALL" - name: "&aIgnite &7(Right Click)" + - material: BOAT + name: '&aBuff Banner &7(Right Click)' lore: - - "&7Ignites players on fire." + - '&7Provides nearby teammates with:' + - '&8┃ &b+50% damage boost' + - '&8┃ &cEffect lasts for 5 seconds' + - '&8┃ &cCharge gained with damage dealt' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWFmNTc2NDU0Y2I2NDFhNmU1OTVlZGY0ZTc3YTcwYzIwM2U4OGVjYWIwZjIyMGQzZmUzMGZiM2NjYzhjOGJhOCJ9fX0=" - name: "&fPyro's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0aW1lc3RhbXAiOjE1MjQyNDkyNDIxNjAsInByb2ZpbGVJZCI6IjdkYTJhYjNhOTNjYTQ4ZWU4MzA0OGFmYzNiODBlNjhlIiwicHJvZmlsZU5hbWUiOiJHb2xkYXBmZWwiLCJzaWduYXR1cmVSZXF1aXJlZCI6dHJ1ZSwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzNkN2UyZmUyZWVhYmZlNTFlYmIzNzhjYjIxNTI3NjgxNGI0MzQ0ZjU2MzZjOGQxODU3N2E0ZTY2Nzg4YzJkNDcifX19 + name: '&fSoldier''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "GOLD_CHESTPLATE" - name: "&fPyro's Chestplate" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fSoldier''s Chestplate' + color: 'B63A3A' + lore: [] unbreakable: true hide-info: true leggings: - material: "CHAINMAIL_LEGGINGS" - name: "&fPyro's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fSoldier''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "CHAINMAIL_BOOTS" - name: "&fPyro's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fSoldier''s Boots' + lore: [] unbreakable: true hide-info: true tank: @@ -540,63 +722,63 @@ kitpvp: ability: cooldown: 30 duration: 5 - display-item: "DIAMOND_CHESTPLATE" + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.tank" + name: kitpvp.kit.tank default: true lore: - - "&7Style: &aDefensive" - - "" - - "&7Slow but very resistant." + - '&7Style: &aDefensive' + - '' + - '&7Slow but very resistant.' effects: - - type: "SLOW" + - type: SLOW items: - - material: "WOOD_AXE" - name: "&aTank's Axe" + - material: WOOD_AXE + name: '&aTank''s Axe' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &c-40% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &c-40% damage penalty' unbreakable: true hide-info: true - - material: "IRON_SPADE" - name: "&aTank's Shovel" + - material: IRON_SPADE + name: '&aTank''s Shovel' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &b-40% damage from ranged damage while active" - - "&8\u2503 &c+40% damage from melee damage while active" - - "&8\u2503 &c-40% damage penalty" + - '&7Compared to Stone Sword:' + - '&8┃ &b-40% damage from ranged damage while active' + - '&8┃ &c+40% damage from melee damage while active' + - '&8┃ &c-40% damage penalty' unbreakable: true hide-info: true - - material: "ANVIL" - name: "&aFortify &7(Right Click)" + - material: ANVIL + name: '&aFortify &7(Right Click)' lore: - - "&7Get a temporary resistance boost." + - '&7Get a temporary resistance boost.' unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTY1OWIyYmIwNzBjMTIwOGJhNTE0NTIzNjFmZDMwYTY2NzIxMzI5NWYyMWRiNDM3ZGY1NzI4MWQ1ODJjODlhZCJ9fX0=" - name: "&fTank's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTY1OWIyYmIwNzBjMTIwOGJhNTE0NTIzNjFmZDMwYTY2NzIxMzI5NWYyMWRiNDM3ZGY1NzI4MWQ1ODJjODlhZCJ9fX0= + name: '&fTank''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "DIAMOND_CHESTPLATE" - name: "&fTank's Chestplate" - lore: [ ] + material: DIAMOND_CHESTPLATE + name: '&fTank''s Chestplate' + lore: [] unbreakable: true hide-info: true leggings: - material: "CHAINMAIL_LEGGINGS" - name: "&fTank's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fTank''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "CHAINMAIL_BOOTS" - name: "&fTank's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fTank''s Boots' + lore: [] unbreakable: true hide-info: true vampire: @@ -605,53 +787,54 @@ kitpvp: ability: cooldown: 30 duration: 5 - display-item: "REDSTONE" + display-item: SKULL_ITEM permission: - name: "kitpvp.kit.vampire" + name: kitpvp.kit.vampire default: true lore: - - "&7Style: &aMixed" - - "" - - "&7Get life-steal on hit." - effects: [ ] + - '&7Style: &aMixed' + - '' + - '&7Get life-steal on hit.' + effects: [] items: - - material: "STONE_SWORD" - name: "&aVampire's Sword" + - material: STONE_SWORD + name: '&aVampire''s Sword' lore: - - "&7Compared to Stone Sword:" - - "&8\u2503 &7No notable changes." + - '&7Compared to Stone Sword:' + - '&8┃ &7No notable changes.' unbreakable: true hide-info: true - - material: "REDSTONE" - name: "&aLife-Steal &7(Right Click)" + - material: INK_SACK + name: '&aShadow Sneak &7(Right Click)' lore: - - "&7Get life-steal on hit." + - '&7Temporarily vanish from sight.' + durability: 8 unbreakable: true hide-info: true armor: helmet: - material: "SKULL_ITEM" - base64: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQ0NDc1NmUwYjRlY2U4ZDc0NjI5NmEzZDVlMjk3ZTE0MTVmNGJhMTc2NDdmZmUyMjgzODUzODNkMTYxYTkifX19" - name: "&fVampire's Head" - lore: [ ] + material: SKULL_ITEM + base64: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGQ0NDc1NmUwYjRlY2U4ZDc0NjI5NmEzZDVlMjk3ZTE0MTVmNGJhMTc2NDdmZmUyMjgzODUzODNkMTYxYTkifX19 + name: '&fVampire''s Head' + lore: [] unbreakable: true hide-info: true chestplate: - material: "LEATHER_CHESTPLATE" - name: "&fVampire's Chestplate" - color: "191919" - lore: [ ] + material: LEATHER_CHESTPLATE + name: '&fVampire''s Chestplate' + color: '191919' + lore: [] unbreakable: true hide-info: true leggings: - material: "IRON_LEGGINGS" - name: "&fVampire's Leggings" - lore: [ ] + material: IRON_LEGGINGS + name: '&fVampire''s Leggings' + lore: [] unbreakable: true hide-info: true boots: - material: "IRON_BOOTS" - name: "&fVampire's Boots" - lore: [ ] + material: IRON_BOOTS + name: '&fVampire''s Boots' + lore: [] unbreakable: true hide-info: true