From 725274453ef078a0c834a5312917b7746641ca1f Mon Sep 17 00:00:00 2001 From: Koopa <115321970+KoopaCode@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:42:13 -0500 Subject: [PATCH] Release 1.0.0 --- .gitignore | 38 +++ README.md | 35 ++- pom.xml | 74 ++++++ .../java/com/example/testplugin/Main.java | 15 ++ .../com/example/testplugin/TestPlugin.java | 28 ++ .../koopa/lifestealcore/LifeStealCore.java | 77 ++++++ .../commands/LifeStealCommand.java | 219 ++++++++++++++++ .../koopa/lifestealcore/gui/ConfigGUI.java | 188 ++++++++++++++ .../koopa/lifestealcore/gui/RevivalGUI.java | 60 +++++ .../listeners/BeaconListener.java | 96 +++++++ .../lifestealcore/listeners/GUIListener.java | 245 ++++++++++++++++++ .../listeners/PlayerListener.java | 83 ++++++ .../lifestealcore/managers/BanManager.java | 179 +++++++++++++ .../lifestealcore/managers/HeartManager.java | 90 +++++++ .../lifestealcore/utils/ItemManager.java | 36 +++ .../lifestealcore/utils/MessageUtils.java | 39 +++ .../lifestealcore/utils/RecipeManager.java | 52 ++++ .../lifestealcore/utils/VersionChecker.java | 116 +++++++++ .../lifestealcore/utils/VersionSupport.java | 36 +++ src/main/resources/config.yml | 61 +++++ src/main/resources/hearts.yml | 2 + src/main/resources/plugin.yml | 36 +++ 22 files changed, 1803 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/example/testplugin/Main.java create mode 100644 src/main/java/com/example/testplugin/TestPlugin.java create mode 100644 src/main/java/com/koopa/lifestealcore/LifeStealCore.java create mode 100644 src/main/java/com/koopa/lifestealcore/commands/LifeStealCommand.java create mode 100644 src/main/java/com/koopa/lifestealcore/gui/ConfigGUI.java create mode 100644 src/main/java/com/koopa/lifestealcore/gui/RevivalGUI.java create mode 100644 src/main/java/com/koopa/lifestealcore/listeners/BeaconListener.java create mode 100644 src/main/java/com/koopa/lifestealcore/listeners/GUIListener.java create mode 100644 src/main/java/com/koopa/lifestealcore/listeners/PlayerListener.java create mode 100644 src/main/java/com/koopa/lifestealcore/managers/BanManager.java create mode 100644 src/main/java/com/koopa/lifestealcore/managers/HeartManager.java create mode 100644 src/main/java/com/koopa/lifestealcore/utils/ItemManager.java create mode 100644 src/main/java/com/koopa/lifestealcore/utils/MessageUtils.java create mode 100644 src/main/java/com/koopa/lifestealcore/utils/RecipeManager.java create mode 100644 src/main/java/com/koopa/lifestealcore/utils/VersionChecker.java create mode 100644 src/main/java/com/koopa/lifestealcore/utils/VersionSupport.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/hearts.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 870f7f4..1070893 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,33 @@ -# LifeSteal-Core -Minecraft Spigot Native 1.20 Plugin for a advanced standalone Lifesteal plugin +# LifeSteal Core ❤ + +A dramatic and feature-rich LifeSteal plugin for Minecraft servers. Take hearts from your victims, craft revival beacons, and experience epic death announcements! + +![Banner](https://r2.e-z.host/12f8b00f-0f0d-48e2-ba84-a304627a1143/6funsf86.png) + +## ✦ Features +- **Heart Stealing System**: Take hearts from players you kill +- **Dramatic Effects**: Server-wide announcements with sound effects +- **Revival Beacons**: Custom crafting system to revive banned players +- **Advanced Configuration**: In-game GUI for all settings +- **Multiple Difficulties**: Easy, Medium, Hard, and Custom recipes +- **Permission Based**: Full control over all features + +## ✦ Downloads +Pre-compiled builds are available at: +- [SpigotMC](https://www.spigotmc.org/resources/lifesteal-core.xxxxx/) +- [GitHub Releases](https://github.com/KoopaCode/LifeSteal-Core/releases) + +## ✦ Requirements +- Server Version: 1.13 - 1.20.4 +- Java: 17 or newer +- Dependencies: None + +## ✦ Installation +1. Download the plugin +2. Place in your plugins folder +3. Start/Restart your server +4. Configure using `/lifesteal config` + +## ✦ Building from Source +If you want to build the plugin yourself: +- ` mvn clean package` diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..75a3469 --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + com.koopa + lifestealcore + 1.0.0 + jar + + LifeStealCore + + + 17 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + org.spigotmc + spigot-api + 1.16.5-R0.1-SNAPSHOT + provided + + + \ No newline at end of file diff --git a/src/main/java/com/example/testplugin/Main.java b/src/main/java/com/example/testplugin/Main.java new file mode 100644 index 0000000..397b164 --- /dev/null +++ b/src/main/java/com/example/testplugin/Main.java @@ -0,0 +1,15 @@ +package com.example.testplugin; + +import org.bukkit.plugin.java.JavaPlugin; + +public class Main extends JavaPlugin { + @Override + public void onEnable() { + getLogger().info("TestPlugin has been enabled!"); + } + + @Override + public void onDisable() { + getLogger().info("TestPlugin has been disabled!"); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/testplugin/TestPlugin.java b/src/main/java/com/example/testplugin/TestPlugin.java new file mode 100644 index 0000000..38ef9ba --- /dev/null +++ b/src/main/java/com/example/testplugin/TestPlugin.java @@ -0,0 +1,28 @@ +package com.example.testplugin; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +public class TestPlugin extends JavaPlugin { + + @Override + public void onEnable() { + getLogger().info("TestPlugin has been enabled!"); + } + + @Override + public void onDisable() { + getLogger().info("TestPlugin has been disabled!"); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (command.getName().equalsIgnoreCase("test")) { + sender.sendMessage(ChatColor.GREEN + "Test command executed successfully!"); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/LifeStealCore.java b/src/main/java/com/koopa/lifestealcore/LifeStealCore.java new file mode 100644 index 0000000..540f44c --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/LifeStealCore.java @@ -0,0 +1,77 @@ +package com.koopa.lifestealcore; + +import org.bukkit.plugin.java.JavaPlugin; +import com.koopa.lifestealcore.commands.LifeStealCommand; +import com.koopa.lifestealcore.listeners.PlayerListener; +import com.koopa.lifestealcore.managers.HeartManager; +import com.koopa.lifestealcore.listeners.GUIListener; +import com.koopa.lifestealcore.managers.BanManager; +import com.koopa.lifestealcore.utils.RecipeManager; +import com.koopa.lifestealcore.listeners.BeaconListener; +import com.koopa.lifestealcore.utils.VersionChecker; +import com.koopa.lifestealcore.utils.VersionSupport; +import org.bukkit.Bukkit; + +public class LifeStealCore extends JavaPlugin { + private HeartManager heartManager; + private BanManager banManager; + private static LifeStealCore instance; + + @Override + public void onEnable() { + instance = this; + + // Check version compatibility + if (!VersionSupport.isSupported()) { + getLogger().severe("§8§l[§c§lLifeSteal§8§l] §cThis plugin requires Minecraft 1.13 or newer!"); + getLogger().severe("§8§l[§c§lLifeSteal§8§l] §cDisabling plugin..."); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + // Log server version info + getLogger().info("§8§l[§c§lLifeSteal§8§l] §aDetected server version: " + VersionSupport.getServerVersion()); + + // Save default config + saveDefaultConfig(); + + // Initialize managers + heartManager = new HeartManager(this); + banManager = new BanManager(this); + + // Register recipes + new RecipeManager(this).registerRecipes(); + + // Register commands + getCommand("lifesteal").setExecutor(new LifeStealCommand(this)); + + // Register listeners + getServer().getPluginManager().registerEvents(new PlayerListener(this), this); + getServer().getPluginManager().registerEvents(new GUIListener(this), this); + getServer().getPluginManager().registerEvents(new BeaconListener(this), this); + + // Add version checker + new VersionChecker(this).checkVersion(); + + getLogger().info("LifeStealCore has been enabled!"); + } + + @Override + public void onDisable() { + // Save all player data + heartManager.saveAllData(); + getLogger().info("LifeStealCore has been disabled!"); + } + + public HeartManager getHeartManager() { + return heartManager; + } + + public BanManager getBanManager() { + return banManager; + } + + public static LifeStealCore getInstance() { + return instance; + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/commands/LifeStealCommand.java b/src/main/java/com/koopa/lifestealcore/commands/LifeStealCommand.java new file mode 100644 index 0000000..80ee796 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/commands/LifeStealCommand.java @@ -0,0 +1,219 @@ +package com.koopa.lifestealcore.commands; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.utils.ItemManager; +import com.koopa.lifestealcore.utils.MessageUtils; +import com.koopa.lifestealcore.gui.ConfigGUI; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.Bukkit; + +import java.util.HashMap; +import java.util.UUID; + +public class LifeStealCommand implements CommandExecutor { + private final LifeStealCore plugin; + private final HashMap resetConfirmMap = new HashMap<>(); + + public LifeStealCommand(LifeStealCore plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("This command can only be used by players!"); + return true; + } + + if (args.length == 0) { + showHelp(player); + return true; + } + + switch (args[0].toLowerCase()) { + case "help" -> showHelp(player); + case "withdraw" -> handleWithdraw(player, args); + case "deposit" -> handleDeposit(player); + case "reset" -> handleReset(player); + case "giveheart" -> handleGiveHeart(player, args); + case "recipe" -> handleRecipe(player); + case "config" -> handleConfig(player); + case "debug" -> handleDebugCommand(player, args); + default -> player.sendMessage(MessageUtils.color("&cUnknown command! Use /lifesteal help")); + } + + return true; + } + + private void showHelp(Player player) { + player.sendMessage(MessageUtils.color("&8&l=== &c&lLifeSteal Commands &8&l===")); + player.sendMessage(MessageUtils.color("&c/lifesteal help &8- &7Show this help menu")); + player.sendMessage(MessageUtils.color("&c/lifesteal withdraw &8- &7Convert hearts to items")); + player.sendMessage(MessageUtils.color("&c/lifesteal deposit &8- &7Deposit heart items")); + player.sendMessage(MessageUtils.color("&c/lifesteal reset &8- &7Reset all players' hearts")); + player.sendMessage(MessageUtils.color("&c/lifesteal giveheart &8- &7Give yourself heart items")); + player.sendMessage(MessageUtils.color("&c/lifesteal recipe &8- &7View the revival beacon recipe")); + if (player.hasPermission("lifesteal.admin.config")) { + player.sendMessage(MessageUtils.color("&c/lifesteal config &8- &7Open configuration GUI")); + } + if (player.hasPermission("lifesteal.admin.debug")) { + player.sendMessage(MessageUtils.color("&c/lifesteal debug &8- &7Test ban/unban messages")); + } + } + + private void handleWithdraw(Player player, String[] args) { + if (!player.hasPermission("lifesteal.withdraw")) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.no-permission"))); + return; + } + + if (args.length != 2) { + player.sendMessage(MessageUtils.color("&cUsage: /lifesteal withdraw ")); + return; + } + + try { + int amount = Integer.parseInt(args[1]); + int currentHearts = plugin.getHeartManager().getPlayerHearts(player); + int minHearts = plugin.getConfig().getInt("settings.min-hearts"); + + if (currentHearts - amount < minHearts) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.minimum-hearts-reached") + .replace("%min%", String.valueOf(minHearts)))); + return; + } + + plugin.getHeartManager().setPlayerHearts(player, currentHearts - amount); + ItemStack heartItem = ItemManager.createHeartItem(amount); + player.getInventory().addItem(heartItem); + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.hearts-withdrawn") + .replace("%amount%", String.valueOf(amount)))); + + } catch (NumberFormatException e) { + player.sendMessage(MessageUtils.color("&cPlease enter a valid number!")); + } + } + + private void handleDeposit(Player player) { + if (!player.hasPermission("lifesteal.deposit")) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.no-permission"))); + return; + } + + int deposited = 0; + int maxHearts = plugin.getConfig().getInt("settings.max-hearts"); + int currentHearts = plugin.getHeartManager().getPlayerHearts(player); + + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && ItemManager.isHeartItem(item)) { + if (currentHearts + deposited + item.getAmount() <= maxHearts) { + deposited += item.getAmount(); + item.setAmount(0); + } else { + int canDeposit = maxHearts - (currentHearts + deposited); + if (canDeposit > 0) { + deposited += canDeposit; + item.setAmount(item.getAmount() - canDeposit); + } + break; + } + } + } + + if (deposited > 0) { + plugin.getHeartManager().setPlayerHearts(player, currentHearts + deposited); + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.hearts-deposited") + .replace("%amount%", String.valueOf(deposited)))); + } else { + player.sendMessage(MessageUtils.color("&cNo hearts found in your inventory!")); + } + } + + private void handleReset(Player player) { + if (!player.hasPermission("lifesteal.admin.reset")) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.no-permission"))); + return; + } + + int defaultHearts = plugin.getConfig().getInt("settings.default-hearts"); + for (Player onlinePlayer : plugin.getServer().getOnlinePlayers()) { + plugin.getHeartManager().setPlayerHearts(onlinePlayer, defaultHearts); + } + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.hearts-reset"))); + } + + private void handleGiveHeart(Player player, String[] args) { + if (!player.hasPermission("lifesteal.admin.give")) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.no-permission"))); + return; + } + + if (args.length != 2) { + player.sendMessage(MessageUtils.color("&cUsage: /lifesteal giveheart ")); + return; + } + + try { + int amount = Integer.parseInt(args[1]); + ItemStack heartItem = ItemManager.createHeartItem(amount); + player.getInventory().addItem(heartItem); + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.hearts-given") + .replace("%amount%", String.valueOf(amount)) + .replace("%player%", player.getName()))); + } catch (NumberFormatException e) { + player.sendMessage(MessageUtils.color("&cPlease enter a valid number!")); + } + } + + private void handleRecipe(Player player) { + new ConfigGUI(plugin).openRecipeGUI(player, plugin.getConfig().getString("settings.difficulty", "MEDIUM")); + } + + private void handleConfig(Player player) { + if (!player.hasPermission("lifesteal.admin.config")) { + player.sendMessage(MessageUtils.color("&cYou don't have permission to use this command!")); + return; + } + new ConfigGUI(plugin).openConfigGUI(player); + } + + private void handleDebugCommand(Player player, String[] args) { + if (!player.hasPermission("lifesteal.admin.debug")) { + player.sendMessage(MessageUtils.color("&cYou don't have permission to use debug commands!")); + return; + } + + if (args.length < 3) { + player.sendMessage(MessageUtils.color("&cUsage: /lifesteal debug ")); + return; + } + + String action = args[1].toLowerCase(); + String targetName = args[2]; + Player target = Bukkit.getPlayer(targetName); + + switch (action) { + case "ban" -> { + if (target == null) { + player.sendMessage(MessageUtils.color("&cPlayer not found!")); + return; + } + // Test ban message with both killer and victim + plugin.getBanManager().banPlayer(target, player); + player.sendMessage(MessageUtils.color("&aDebug: Tested ban message for " + target.getName() + + " (killed by " + player.getName() + ")")); + } + case "unban" -> { + // Test unban/revival message + plugin.getBanManager().unbanPlayer(player, targetName); + player.sendMessage(MessageUtils.color("&aDebug: Tested unban message for " + targetName + + " (revived by " + player.getName() + ")")); + } + default -> player.sendMessage(MessageUtils.color("&cInvalid debug action! Use 'ban' or 'unban'")); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/gui/ConfigGUI.java b/src/main/java/com/koopa/lifestealcore/gui/ConfigGUI.java new file mode 100644 index 0000000..5b7452f --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/gui/ConfigGUI.java @@ -0,0 +1,188 @@ +package com.koopa.lifestealcore.gui; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ConfigGUI { + private final LifeStealCore plugin; + private static final String GUI_TITLE = "§8LifeSteal Configuration"; + private static final String RECIPE_GUI_TITLE = "§8Revival Beacon Recipe"; + + public ConfigGUI(LifeStealCore plugin) { + this.plugin = plugin; + } + + public void openConfigGUI(Player player) { + Inventory gui = Bukkit.createInventory(null, 36, GUI_TITLE); + + // Fill with gray stained glass panes + for (int i = 0; i < gui.getSize(); i++) { + gui.setItem(i, createConfigItem(Material.GRAY_STAINED_GLASS_PANE, " ")); + } + + // Default Hearts Setting (Heart of the Sea) + gui.setItem(11, createConfigItem(Material.HEART_OF_THE_SEA, + "&c&lDefault Hearts", + "&7Current value: &f" + plugin.getConfig().getInt("settings.default-hearts"), + "&7", + "&7Click to change the default", + "&7hearts for new players")); + + // Min Hearts Setting (Redstone) + gui.setItem(13, createConfigItem(Material.REDSTONE, + "&c&lMinimum Hearts", + "&7Current value: &f" + plugin.getConfig().getInt("settings.min-hearts"), + "&7", + "&7Click to change the minimum", + "&7hearts a player can have")); + + // Max Hearts Setting (Diamond) + gui.setItem(15, createConfigItem(Material.DIAMOND, + "&c&lMaximum Hearts", + "&7Current value: &f" + plugin.getConfig().getInt("settings.max-hearts"), + "&7", + "&7Click to change the maximum", + "&7hearts a player can have")); + + // Heart Item Preview (Nether Star) + gui.setItem(21, createConfigItem(Material.NETHER_STAR, + plugin.getConfig().getString("settings.heart-item-name"), + "&7", + "&7Current heart item settings:", + "&7" + String.join("&7", plugin.getConfig().getStringList("settings.heart-item-lore")))); + + // Difficulty Selector (Netherite Ingot) + ItemStack difficultyItem = new ItemStack(Material.CRAFTING_TABLE); + ItemMeta diffMeta = difficultyItem.getItemMeta(); + String currentDifficulty = plugin.getConfig().getString("settings.difficulty", "MEDIUM"); + diffMeta.setDisplayName(MessageUtils.color("&c&lCrafting Difficulty")); + diffMeta.setLore(Arrays.asList( + MessageUtils.color("&7Current: &f" + currentDifficulty), + MessageUtils.color("&7"), + MessageUtils.color("&7Left-Click to change"), + MessageUtils.color("&7Right-Click to view recipe") + )); + difficultyItem.setItemMeta(diffMeta); + gui.setItem(23, difficultyItem); + + // Save & Reload button (Emerald) + gui.setItem(31, createConfigItem(Material.EMERALD, + "&a&lSave & Reload", + "&7", + "&7Click to save changes", + "&7and reload the config")); + + player.openInventory(gui); + } + + public void openRecipeGUI(Player player, String difficulty) { + Inventory gui = Bukkit.createInventory(null, 27, RECIPE_GUI_TITLE); + String configPath = "settings.recipe-materials." + difficulty; + + // Fill with gray stained glass panes first + for (int i = 0; i < gui.getSize(); i++) { + gui.setItem(i, createConfigItem(Material.GRAY_STAINED_GLASS_PANE, " ")); + } + + // Get recipe materials + Material cornerMaterial = difficulty.equals("EASY") ? + Material.NETHER_STAR : // Hearts for EASY mode + Material.valueOf(plugin.getConfig().getString(configPath + ".top_corners")); + Material centerMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".center_row")); + Material skullMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".skull")); + Material bottomMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".bottom")); + + // Set recipe items + ItemStack corner = difficulty.equals("EASY") ? + createConfigItem(Material.NETHER_STAR, "&c❤ Heart", "&7Required: 2") : + createConfigItem(cornerMaterial, "&f" + cornerMaterial.name(), "&7Required: 2"); + ItemStack center = createConfigItem(centerMaterial, "&f" + centerMaterial.name(), "&7Required: 3"); + ItemStack skull = createConfigItem(skullMaterial, "&f" + skullMaterial.name(), "&7Required: 1"); + ItemStack bottom = createConfigItem(bottomMaterial, "&f" + bottomMaterial.name(), "&7Required: 3"); + + // Place items in crafting grid pattern (original layout) + gui.setItem(3, corner); // Top left + gui.setItem(4, center); // Top middle + gui.setItem(5, corner); // Top right + gui.setItem(12, center); // Middle left + gui.setItem(13, skull); // Center + gui.setItem(14, center); // Middle right + gui.setItem(21, bottom); // Bottom left + gui.setItem(22, bottom); // Bottom middle + gui.setItem(23, bottom); // Bottom right + + // Result item + ItemStack result = createConfigItem(Material.BEACON, "&c&lRevival Beacon", + "&7The result of the recipe", + "&7in " + difficulty + " mode"); + gui.setItem(16, result); + + // Back button + gui.setItem(18, createConfigItem(Material.ARROW, "&c&lBack to Settings", + "&7Click to return to the settings menu")); + + player.openInventory(gui); + } + + public void openCustomRecipeGUI(Player player) { + Inventory gui = Bukkit.createInventory(null, 54, "§8Custom Recipe Editor"); + + // Fill with gray stained glass panes + for (int i = 0; i < gui.getSize(); i++) { + gui.setItem(i, createConfigItem(Material.GRAY_STAINED_GLASS_PANE, " ")); + } + + // Create empty crafting grid + int[] craftingSlots = {11, 12, 13, 20, 21, 22, 29, 30, 31}; + for (int slot : craftingSlots) { + gui.setItem(slot, null); // Empty slots for items + } + + // Result item (Beacon) + gui.setItem(24, createConfigItem(Material.BEACON, "&c&lRevival Beacon", + "&7The crafting result")); + + // Save button + gui.setItem(49, createConfigItem(Material.EMERALD, "&a&lSave Recipe", + "&7Click to save your custom recipe")); + + // Back button + gui.setItem(45, createConfigItem(Material.ARROW, "&c&lBack", + "&7Return to settings")); + + player.openInventory(gui); + } + + private boolean isInCraftingGrid(int slot) { + int[] craftingSlots = {11, 12, 13, 20, 21, 22, 29, 30, 31}; + for (int craftingSlot : craftingSlots) { + if (slot == craftingSlot) return true; + } + return false; + } + + private ItemStack createConfigItem(Material material, String name, String... lore) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(MessageUtils.color(name)); + + List loreList = new ArrayList<>(); + for (String line : lore) { + loreList.add(MessageUtils.color(line)); + } + meta.setLore(loreList); + + item.setItemMeta(meta); + return item; + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/gui/RevivalGUI.java b/src/main/java/com/koopa/lifestealcore/gui/RevivalGUI.java new file mode 100644 index 0000000..0966cff --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/gui/RevivalGUI.java @@ -0,0 +1,60 @@ +package com.koopa.lifestealcore.gui; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import java.util.Arrays; +import java.util.List; + +public class RevivalGUI { + private static final String GUI_TITLE = "§c§lRevive Banned Player"; + private final Location beaconLocation; + + public RevivalGUI(Location beaconLocation) { + this.beaconLocation = beaconLocation; + } + + public void openGUI(Player player) { + List bannedPlayers = LifeStealCore.getInstance().getBanManager().getBannedPlayers(); + + // Always create at least a 9-slot inventory + int size = bannedPlayers.isEmpty() ? 9 : Math.min(((bannedPlayers.size() + 8) / 9) * 9, 54); + Inventory gui = Bukkit.createInventory(null, size, GUI_TITLE); + + if (bannedPlayers.isEmpty()) { + // Add an item to show there are no banned players + ItemStack noPlayers = new ItemStack(Material.BARRIER); + ItemMeta meta = noPlayers.getItemMeta(); + meta.setDisplayName(MessageUtils.color("&cNo banned players!")); + meta.setLore(Arrays.asList( + MessageUtils.color("&7There are currently no"), + MessageUtils.color("&7banned players to revive.") + )); + noPlayers.setItemMeta(meta); + gui.setItem(4, noPlayers); + } else { + // Add banned player heads + for (String playerName : bannedPlayers) { + ItemStack skull = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + meta.setDisplayName(MessageUtils.color("&c" + playerName)); + meta.setLore(Arrays.asList( + MessageUtils.color("&7Click to revive this player"), + MessageUtils.color("&7They will spawn at this beacon") + )); + meta.setOwner(playerName); + skull.setItemMeta(meta); + gui.addItem(skull); + } + } + + player.openInventory(gui); + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/listeners/BeaconListener.java b/src/main/java/com/koopa/lifestealcore/listeners/BeaconListener.java new file mode 100644 index 0000000..1301aaf --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/listeners/BeaconListener.java @@ -0,0 +1,96 @@ +package com.koopa.lifestealcore.listeners; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.gui.RevivalGUI; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +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.block.BlockPlaceEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; + +public class BeaconListener implements Listener { + private final LifeStealCore plugin; + private Location lastBeaconLocation; + + public BeaconListener(LifeStealCore plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onBeaconPlace(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + if (item.getType() != Material.BEACON || !item.hasItemMeta() || + !item.getItemMeta().getDisplayName().equals(MessageUtils.color("&c&lRevival Beacon"))) { + return; + } + + lastBeaconLocation = event.getBlock().getLocation(); + new RevivalGUI(lastBeaconLocation).openGUI(event.getPlayer()); + } + + @EventHandler + public void onGUIClick(InventoryClickEvent event) { + if (!event.getView().getTitle().equals("§c§lRevive Banned Player")) return; + event.setCancelled(true); + + if (!(event.getWhoClicked() instanceof Player player)) return; + + ItemStack clicked = event.getCurrentItem(); + if (clicked == null) return; + + // If it's the "no players" barrier, just return + if (clicked.getType() == Material.BARRIER) { + player.closeInventory(); + return; + } + + // Handle player head clicks + if (clicked.getType() == Material.PLAYER_HEAD) { + String playerName = clicked.getItemMeta().getDisplayName().substring(2); // Remove color code + + // Remove the beacon block + if (lastBeaconLocation != null) { + Block beacon = lastBeaconLocation.getBlock(); + if (beacon.getType() == Material.BEACON) { + beacon.setType(Material.AIR); + } + } + + plugin.getBanManager().revivePlayer(playerName, lastBeaconLocation); + player.sendMessage(MessageUtils.color("&aPlayer has been revived!")); + player.closeInventory(); + } + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + Location revivalLoc = plugin.getBanManager().getRevivalLocation(player.getUniqueId()); + + if (revivalLoc != null) { + player.teleport(revivalLoc); + plugin.getHeartManager().setPlayerHearts(player, 5); + player.sendMessage(MessageUtils.color("&aYou have been revived! You have 5 hearts.")); + } + } + + // Cancel right-clicking the beacon to prevent the vanilla beacon GUI + @EventHandler + public void onBeaconInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (event.getClickedBlock() == null || event.getClickedBlock().getType() != Material.BEACON) return; + + // Check if it's our revival beacon by location + if (lastBeaconLocation != null && lastBeaconLocation.equals(event.getClickedBlock().getLocation())) { + event.setCancelled(true); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/listeners/GUIListener.java b/src/main/java/com/koopa/lifestealcore/listeners/GUIListener.java new file mode 100644 index 0000000..c94eb42 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/listeners/GUIListener.java @@ -0,0 +1,245 @@ +package com.koopa.lifestealcore.listeners; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.gui.ConfigGUI; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.HashMap; +import java.util.UUID; + +public class GUIListener implements Listener { + private final LifeStealCore plugin; + private static final String GUI_TITLE = "§8LifeSteal Configuration"; + private final HashMap editingPlayers = new HashMap<>(); + private final HashMap materialEditing = new HashMap<>(); + + public GUIListener(LifeStealCore plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + String title = event.getView().getTitle(); + + // Handle Config GUI + if (title.equals("§8LifeSteal Configuration")) { + event.setCancelled(true); + handleConfigGUI(event); + return; + } + + // Handle Recipe View GUI + if (title.equals("§8Revival Beacon Recipe")) { + event.setCancelled(true); + if (event.getSlot() == 18) { // Back button + new ConfigGUI(plugin).openConfigGUI((Player) event.getWhoClicked()); + } + return; + } + + // Handle Custom Recipe Editor + if (title.equals("§8Custom Recipe Editor")) { + handleCustomRecipeEditor(event); + return; + } + } + + private void handleCustomRecipeEditor(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) return; + if (!player.hasPermission("lifesteal.admin.config")) return; + + int slot = event.getSlot(); + int[] craftingSlots = {11, 12, 13, 20, 21, 22, 29, 30, 31}; + + // Allow clicking in crafting grid + if (isInArray(slot, craftingSlots)) { + event.setCancelled(false); + return; + } + + // Handle save button + if (slot == 49) { + event.setCancelled(true); + saveCustomRecipe(event.getInventory()); + player.sendMessage(MessageUtils.color("&aCustom recipe saved!")); + new ConfigGUI(plugin).openConfigGUI(player); + return; + } + + // Handle back button + if (slot == 45) { + event.setCancelled(true); + new ConfigGUI(plugin).openConfigGUI(player); + return; + } + + // Cancel all other clicks in the GUI + if (event.getClickedInventory() != null && + event.getView().getTitle().equals("§8Custom Recipe Editor")) { + event.setCancelled(true); + } + } + + private boolean isInArray(int value, int[] array) { + for (int i : array) { + if (i == value) return true; + } + return false; + } + + private ItemStack createEmptySlot() { + ItemStack item = new ItemStack(Material.BLACK_STAINED_GLASS_PANE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(MessageUtils.color("&7Click to set item")); + item.setItemMeta(meta); + return item; + } + + private void handleConfigGUI(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) return; + if (!player.hasPermission("lifesteal.admin.config")) return; + + switch (event.getSlot()) { + case 11 -> promptForValue(player, "default-hearts", "Enter new default hearts value:"); + case 13 -> promptForValue(player, "min-hearts", "Enter new minimum hearts value:"); + case 15 -> promptForValue(player, "max-hearts", "Enter new maximum hearts value:"); + case 23 -> handleDifficultyClick(event); + case 31 -> { + plugin.reloadConfig(); + player.sendMessage(MessageUtils.color("&aConfiguration reloaded!")); + player.closeInventory(); + } + } + } + + private void handleDifficultyClick(InventoryClickEvent event) { + Player player = (Player) event.getWhoClicked(); + if (event.isRightClick()) { + if (plugin.getConfig().getString("settings.difficulty").equals("CUSTOM")) { + new ConfigGUI(plugin).openCustomRecipeGUI(player); + } else { + String currentDiff = plugin.getConfig().getString("settings.difficulty", "MEDIUM"); + new ConfigGUI(plugin).openRecipeGUI(player, currentDiff); + } + } else if (event.isLeftClick()) { + String currentDiff = plugin.getConfig().getString("settings.difficulty", "MEDIUM"); + String newDiff = switch (currentDiff) { + case "EASY" -> "MEDIUM"; + case "MEDIUM" -> "HARD"; + case "HARD" -> "CUSTOM"; + case "CUSTOM" -> "EASY"; + default -> "MEDIUM"; + }; + plugin.getConfig().set("settings.difficulty", newDiff); + plugin.saveConfig(); + new ConfigGUI(plugin).openConfigGUI(player); + player.sendMessage(MessageUtils.color("&aSet crafting difficulty to: " + newDiff)); + } + } + + private void saveCustomRecipe(Inventory gui) { + String configPath = "settings.recipe-materials.CUSTOM"; + + // Get materials from crafting grid + ItemStack cornerItem = gui.getItem(11); + ItemStack centerItem = gui.getItem(12); + ItemStack skullItem = gui.getItem(21); + ItemStack bottomItem = gui.getItem(29); + + // Save to config (checking for null items) + plugin.getConfig().set(configPath + ".top_corners", + cornerItem != null ? cornerItem.getType().name() : "GLASS"); + plugin.getConfig().set(configPath + ".center_row", + centerItem != null ? centerItem.getType().name() : "DIAMOND"); + plugin.getConfig().set(configPath + ".skull", + skullItem != null ? skullItem.getType().name() : "WITHER_SKELETON_SKULL"); + plugin.getConfig().set(configPath + ".bottom", + bottomItem != null ? bottomItem.getType().name() : "OBSIDIAN"); + plugin.saveConfig(); + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (event.getView().getTitle().equals("§8Custom Recipe Editor")) { + event.getPlayer().setItemOnCursor(null); + } + } + + private void promptForValue(Player player, String setting, String prompt) { + player.closeInventory(); + player.sendMessage(MessageUtils.color(prompt)); + editingPlayers.put(player.getUniqueId(), "settings." + setting); + } + + private void promptForMaterial(Player player, String part, String prompt) { + player.closeInventory(); + player.sendMessage(MessageUtils.color(prompt)); + materialEditing.put(player.getUniqueId(), "settings.recipe-materials.CUSTOM." + part); + } + + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + String settingValue = editingPlayers.get(player.getUniqueId()); + String settingMaterial = materialEditing.get(player.getUniqueId()); + + if (settingValue != null) { + event.setCancelled(true); + try { + int value = Integer.parseInt(event.getMessage()); + plugin.getConfig().set(settingValue, value); + plugin.saveConfig(); + player.sendMessage(MessageUtils.color("&aValue updated!")); + + Bukkit.getScheduler().runTask(plugin, () -> { + new ConfigGUI(plugin).openConfigGUI(player); + }); + } catch (NumberFormatException e) { + player.sendMessage(MessageUtils.color("&cPlease enter a valid number!")); + } + editingPlayers.remove(player.getUniqueId()); + } + else if (settingMaterial != null) { + event.setCancelled(true); + if (event.getMessage().equalsIgnoreCase("cancel")) { + player.sendMessage(MessageUtils.color("&cCancelled material selection.")); + Bukkit.getScheduler().runTask(plugin, () -> { + new ConfigGUI(plugin).openCustomRecipeGUI(player); + }); + } else { + try { + Material material = Material.valueOf(event.getMessage().toUpperCase()); + plugin.getConfig().set(settingMaterial, material.name()); + plugin.saveConfig(); + player.sendMessage(MessageUtils.color("&aMaterial updated!")); + + Bukkit.getScheduler().runTask(plugin, () -> { + new ConfigGUI(plugin).openCustomRecipeGUI(player); + }); + } catch (IllegalArgumentException e) { + player.sendMessage(MessageUtils.color("&cInvalid material! Try again or type 'cancel'")); + } + } + materialEditing.remove(player.getUniqueId()); + } + } + + private ItemStack createConfigItem(Material material, String name) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(MessageUtils.color(name)); + item.setItemMeta(meta); + return item; + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/listeners/PlayerListener.java b/src/main/java/com/koopa/lifestealcore/listeners/PlayerListener.java new file mode 100644 index 0000000..ce75bb2 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/listeners/PlayerListener.java @@ -0,0 +1,83 @@ +package com.koopa.lifestealcore.listeners; + +import com.koopa.lifestealcore.LifeStealCore; +import com.koopa.lifestealcore.utils.ItemManager; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class PlayerListener implements Listener { + private final LifeStealCore plugin; + + public PlayerListener(LifeStealCore plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + plugin.getHeartManager().updatePlayerMaxHealth(player); + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player victim = event.getEntity(); + Player killer = victim.getKiller(); + + if (killer != null && killer != victim) { + int victimHearts = plugin.getHeartManager().getPlayerHearts(victim); + int killerHearts = plugin.getHeartManager().getPlayerHearts(killer); + + if (victimHearts > plugin.getConfig().getInt("settings.min-hearts")) { + plugin.getHeartManager().setPlayerHearts(victim, victimHearts - 1); + plugin.getHeartManager().setPlayerHearts(killer, killerHearts + 1); + killer.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 100, 1)); + } + } + + // Ban the player + plugin.getBanManager().banPlayer(victim); + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + + // Check if right-clicking with a heart item + if ((event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) + && ItemManager.isHeartItem(item)) { + event.setCancelled(true); + + int currentHearts = plugin.getHeartManager().getPlayerHearts(player); + int maxHearts = plugin.getConfig().getInt("settings.max-hearts"); + + if (currentHearts >= maxHearts) { + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.maximum-hearts-reached") + .replace("%max%", String.valueOf(maxHearts)))); + return; + } + + // Remove one heart item and add to player's health + item.setAmount(item.getAmount() - 1); + plugin.getHeartManager().setPlayerHearts(player, currentHearts + 1); + player.sendMessage(MessageUtils.color(plugin.getConfig().getString("messages.hearts-deposited") + .replace("%amount%", "1"))); + } + } + + @EventHandler + public void onHeartChange(PlayerJoinEvent event) { + Player player = event.getPlayer(); + // Give regeneration effect when hearts change + player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, 100, 1)); // 5 seconds of Regen II + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/managers/BanManager.java b/src/main/java/com/koopa/lifestealcore/managers/BanManager.java new file mode 100644 index 0000000..76adaba --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/managers/BanManager.java @@ -0,0 +1,179 @@ +package com.koopa.lifestealcore.managers; + +import com.koopa.lifestealcore.LifeStealCore; +import org.bukkit.BanList; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.configuration.file.YamlConfiguration; +import java.io.File; +import java.util.*; +import org.bukkit.Sound; +import com.koopa.lifestealcore.utils.MessageUtils; +import org.bukkit.OfflinePlayer; + +public class BanManager { + private final LifeStealCore plugin; + private final Map bannedPlayers = new HashMap<>(); + private final Map revivalLocations = new HashMap<>(); + private final File banFile; + private YamlConfiguration banData; + + public BanManager(LifeStealCore plugin) { + this.plugin = plugin; + this.banFile = new File(plugin.getDataFolder(), "bans.yml"); + loadBanData(); + } + + private void loadBanData() { + if (!banFile.exists()) { + try { + banFile.createNewFile(); + } catch (Exception e) { + e.printStackTrace(); + } + } + banData = YamlConfiguration.loadConfiguration(banFile); + } + + public void saveBanData() { + try { + banData.save(banFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void banPlayer(Player player) { + banPlayer(player, false); + } + + public void banPlayer(Player player, boolean debugMode) { + bannedPlayers.put(player.getUniqueId(), true); + + // Save to ban data + banData.set(player.getUniqueId().toString() + ".name", player.getName()); + banData.set(player.getUniqueId().toString() + ".banTime", System.currentTimeMillis()); + saveBanData(); + + if (!debugMode) { + // Ban the player + Bukkit.getBanList(BanList.Type.NAME).addBan( + player.getName(), + "§c§lYou died! §7Get someone to revive you with a Revival Beacon!", + null, + "LifeSteal System" + ); + player.kickPlayer("§c§lYou died! §7Get someone to revive you with a Revival Beacon!"); + } + } + + public void revivePlayer(String playerName, Location beaconLoc) { + UUID uuid = null; + for (String key : banData.getKeys(false)) { + if (banData.getString(key + ".name").equalsIgnoreCase(playerName)) { + uuid = UUID.fromString(key); + break; + } + } + + if (uuid != null) { + bannedPlayers.remove(uuid); + revivalLocations.put(uuid, beaconLoc); + banData.set(uuid.toString(), null); + saveBanData(); + } + + Bukkit.getBanList(BanList.Type.NAME).pardon(playerName); + } + + public void revivePlayer(String playerName) { + revivePlayer(playerName, null); + } + + public List getBannedPlayers() { + List players = new ArrayList<>(); + for (String key : banData.getKeys(false)) { + players.add(banData.getString(key + ".name")); + } + return players; + } + + public Location getRevivalLocation(UUID uuid) { + Location loc = revivalLocations.get(uuid); + revivalLocations.remove(uuid); + return loc; + } + + public boolean isPlayerBanned(UUID uuid) { + return bannedPlayers.getOrDefault(uuid, false); + } + + public void banPlayer(Player player, Player killer) { + // Play dramatic sound to all players + for (Player p : Bukkit.getOnlinePlayers()) { + p.playSound(p.getLocation(), Sound.ENTITY_ENDER_DRAGON_GROWL, 1.0f, 0.5f); + } + + // Dramatic ban message + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(MessageUtils.color("&8&l[&c&l!!!&8&l] &c&lA PLAYER HAS FALLEN &8&l[&c&l!!!&8&l]")); + Bukkit.broadcastMessage(MessageUtils.color("&7" + player.getName() + " &8has lost their final heart...")); + if (killer != null) { + Bukkit.broadcastMessage(MessageUtils.color("&7Slain by the hands of &c" + killer.getName())); + } + Bukkit.broadcastMessage(MessageUtils.color("&c&lBANISHED TO THE SHADOW REALM")); + Bukkit.broadcastMessage(""); + + // Ban the player + Bukkit.getScheduler().runTaskLater(plugin, () -> { + player.kickPlayer(MessageUtils.color( + "&c&lYOU HAVE BEEN BANISHED!\n\n" + + "&7You have lost all your hearts...\n" + + "&7Find a Revival Beacon to return!" + )); + plugin.getConfig().set("banned-players." + player.getUniqueId(), true); + plugin.saveConfig(); + }, 2L); // Small delay for dramatic effect + } + + public void unbanPlayer(Player revivedBy, String playerName) { + // Get the banned player's UUID + UUID bannedUUID = null; + String bannedName = playerName; + + for (String uuidStr : plugin.getConfig().getConfigurationSection("banned-players").getKeys(false)) { + UUID uuid = UUID.fromString(uuidStr); + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + if (offlinePlayer.getName().equalsIgnoreCase(playerName)) { + bannedUUID = uuid; + bannedName = offlinePlayer.getName(); + break; + } + } + + if (bannedUUID != null) { + // Play dramatic revival sound + for (Player p : Bukkit.getOnlinePlayers()) { + p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.0f); + p.playSound(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1.0f, 1.0f); + } + + // Dramatic revival message + Bukkit.broadcastMessage(""); + Bukkit.broadcastMessage(MessageUtils.color("&8&l[&e&l!!!&8&l] &e&lA SOUL HAS BEEN REVIVED &8&l[&e&l!!!&8&l]")); + Bukkit.broadcastMessage(MessageUtils.color("&7Through the power of the Revival Beacon...")); + Bukkit.broadcastMessage(MessageUtils.color("&e" + bannedName + " &7has been given another chance!")); + Bukkit.broadcastMessage(MessageUtils.color("&7Restored to life by &e" + revivedBy.getName())); + Bukkit.broadcastMessage(""); + + // Unban the player + plugin.getConfig().set("banned-players." + bannedUUID, null); + plugin.saveConfig(); + + // Reset their hearts to default + plugin.getHeartManager().setHearts(bannedUUID, + plugin.getConfig().getInt("settings.default-hearts", 10)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/managers/HeartManager.java b/src/main/java/com/koopa/lifestealcore/managers/HeartManager.java new file mode 100644 index 0000000..121b152 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/managers/HeartManager.java @@ -0,0 +1,90 @@ +package com.koopa.lifestealcore.managers; + +import com.koopa.lifestealcore.LifeStealCore; +import org.bukkit.entity.Player; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.bukkit.Bukkit; + +public class HeartManager { + private final LifeStealCore plugin; + private final Map playerHearts; + private final File dataFile; + private FileConfiguration data; + + public HeartManager(LifeStealCore plugin) { + this.plugin = plugin; + this.playerHearts = new HashMap<>(); + this.dataFile = new File(plugin.getDataFolder(), "hearts.yml"); + loadData(); + } + + private void loadData() { + if (!dataFile.exists()) { + try { + dataFile.getParentFile().mkdirs(); + dataFile.createNewFile(); + data = new YamlConfiguration(); + data.save(dataFile); + } catch (Exception e) { + plugin.getLogger().severe("Could not create hearts.yml!"); + e.printStackTrace(); + } + } + data = YamlConfiguration.loadConfiguration(dataFile); + } + + public void saveAllData() { + if (data == null) return; + + for (Map.Entry entry : playerHearts.entrySet()) { + data.set(entry.getKey().toString(), entry.getValue()); + } + + try { + data.save(dataFile); + } catch (Exception e) { + plugin.getLogger().severe("Could not save hearts data!"); + e.printStackTrace(); + } + } + + public int getPlayerHearts(Player player) { + return playerHearts.getOrDefault(player.getUniqueId(), + plugin.getConfig().getInt("settings.default-hearts")); + } + + public void setPlayerHearts(Player player, int hearts) { + playerHearts.put(player.getUniqueId(), hearts); + updatePlayerMaxHealth(player); + } + + public void updatePlayerMaxHealth(Player player) { + int hearts = getPlayerHearts(player); + player.setMaxHealth(hearts * 2); + } + + public void setHearts(UUID uuid, int hearts) { + // Ensure hearts are within configured limits + int minHearts = plugin.getConfig().getInt("settings.min-hearts", 1); + int maxHearts = plugin.getConfig().getInt("settings.max-hearts", 20); + + hearts = Math.max(minHearts, Math.min(maxHearts, hearts)); + + // Update hearts in memory and config + playerHearts.put(uuid, hearts); + plugin.getConfig().set("player-hearts." + uuid, hearts); + plugin.saveConfig(); + + // Update player's max health if they're online + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) { + player.setMaxHealth(hearts * 2); + player.setHealth(player.getMaxHealth()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/utils/ItemManager.java b/src/main/java/com/koopa/lifestealcore/utils/ItemManager.java new file mode 100644 index 0000000..e8d2bd1 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/utils/ItemManager.java @@ -0,0 +1,36 @@ +package com.koopa.lifestealcore.utils; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import com.koopa.lifestealcore.LifeStealCore; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ItemManager { + private static final NamespacedKey HEART_KEY = new NamespacedKey(LifeStealCore.getInstance(), "lifesteal_heart"); + + public static ItemStack createHeartItem(int amount) { + ItemStack item = new ItemStack(Material.NETHER_STAR, amount); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(MessageUtils.color("&c❤ Heart")); + meta.setLore(Arrays.asList( + MessageUtils.color("&7Right-click to consume"), + MessageUtils.color("&7and gain an extra heart") + )); + item.setItemMeta(meta); + return item; + } + + public static boolean isHeartItem(ItemStack item) { + if (item == null || item.getType() != Material.NETHER_STAR) return false; + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + return meta.hasDisplayName() && + meta.getDisplayName().equals(MessageUtils.color("&c❤ Heart")); + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/utils/MessageUtils.java b/src/main/java/com/koopa/lifestealcore/utils/MessageUtils.java new file mode 100644 index 0000000..14bf077 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/utils/MessageUtils.java @@ -0,0 +1,39 @@ +package com.koopa.lifestealcore.utils; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import java.util.List; +import java.util.Map; + +public class MessageUtils { + public static String color(String message) { + return ChatColor.translateAlternateColorCodes('&', message); + } + + public static void sendHelpMenu(CommandSender sender, ConfigurationSection config) { + // Send header + sender.sendMessage(color(config.getString("messages.help.header"))); + + // Send command list + String format = config.getString("messages.help.format"); + List> commands = config.getMapList("messages.help.commands"); + + for (Map cmdMap : commands) { + String command = (String) cmdMap.get("command"); + String description = (String) cmdMap.get("description"); + String permission = (String) cmdMap.get("permission"); + + // Only show commands the player has permission for + if (permission == null || sender.hasPermission(permission)) { + String helpLine = format + .replace("%command%", command) + .replace("%description%", description); + sender.sendMessage(color(helpLine)); + } + } + + // Send footer + sender.sendMessage(color(config.getString("messages.help.footer"))); + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/utils/RecipeManager.java b/src/main/java/com/koopa/lifestealcore/utils/RecipeManager.java new file mode 100644 index 0000000..7904f2c --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/utils/RecipeManager.java @@ -0,0 +1,52 @@ +package com.koopa.lifestealcore.utils; + +import com.koopa.lifestealcore.LifeStealCore; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.RecipeChoice; + +import java.util.Arrays; + +public class RecipeManager { + private final LifeStealCore plugin; + private static final NamespacedKey REVIVAL_BEACON_KEY = new NamespacedKey(LifeStealCore.getInstance(), "revival_beacon"); + + public RecipeManager(LifeStealCore plugin) { + this.plugin = plugin; + } + + public void registerRecipes() { + String difficulty = plugin.getConfig().getString("settings.difficulty", "MEDIUM"); + String configPath = "settings.recipe-materials." + difficulty; + + ShapedRecipe recipe = new ShapedRecipe(new NamespacedKey(plugin, "revival_beacon"), new ItemStack(Material.BEACON)); + recipe.shape("CCC", "CSC", "BBB"); + + Material cornerMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".top_corners")); + Material centerMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".center_row")); + Material skullMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".skull")); + Material bottomMaterial = Material.valueOf(plugin.getConfig().getString(configPath + ".bottom")); + + recipe.setIngredient('C', cornerMaterial); + recipe.setIngredient('S', skullMaterial); + recipe.setIngredient('B', bottomMaterial); + + Bukkit.addRecipe(recipe); + } + + public static ItemStack createRevivalBeacon() { + ItemStack beacon = new ItemStack(Material.BEACON); + ItemMeta meta = beacon.getItemMeta(); + meta.setDisplayName(MessageUtils.color("&c&lRevival Beacon")); + meta.setLore(Arrays.asList( + MessageUtils.color("&7Use this beacon to revive"), + MessageUtils.color("&7a banned player!") + )); + beacon.setItemMeta(meta); + return beacon; + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/utils/VersionChecker.java b/src/main/java/com/koopa/lifestealcore/utils/VersionChecker.java new file mode 100644 index 0000000..5994c74 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/utils/VersionChecker.java @@ -0,0 +1,116 @@ +package com.koopa.lifestealcore.utils; + +import com.koopa.lifestealcore.LifeStealCore; +import org.bukkit.Bukkit; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +public class VersionChecker { + private final LifeStealCore plugin; + private static final String VERSION_URL = "https://raw.githubusercontent.com/KoopaCode/LifeSteal-Core/refs/heads/main/version/versioncheck"; + private static final String SPIGOT_URL_FILE = "https://raw.githubusercontent.com/KoopaCode/LifeSteal-Core/refs/heads/main/spigot/url"; + private static final String GITHUB_URL = "https://github.com/KoopaCode/LifeSteal-Core/releases"; + private String spigotUrl = null; + + public VersionChecker(LifeStealCore plugin) { + this.plugin = plugin; + fetchSpigotUrl(); + } + + private void fetchSpigotUrl() { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + URL url = new URL(SPIGOT_URL_FILE); + java.net.URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + conn.addRequestProperty("User-Agent", "Mozilla/5.0"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + spigotUrl = reader.readLine().trim(); + reader.close(); + } catch (Exception e) { + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §cFailed to fetch Spigot URL: " + e.getMessage()); + spigotUrl = "https://www.spigotmc.org/resources/lifesteal-core.xxxxx/"; + } + }); + } + + public void checkVersion() { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + URL url = new URL(VERSION_URL); + java.net.URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + conn.addRequestProperty("User-Agent", "Mozilla/5.0"); + conn.addRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate"); + conn.addRequestProperty("Pragma", "no-cache"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String latestVersion = reader.readLine().trim(); + reader.close(); + + String currentVersion = plugin.getDescription().getVersion(); + int comparison = compareVersions(currentVersion, latestVersion); + + Bukkit.getScheduler().runTask(plugin, () -> { + if (comparison > 0) { + // Running beta/development version (current > latest) + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §e§lYou are running a beta version!"); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §eCurrent version: §f" + currentVersion); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §eLatest stable: §f" + latestVersion); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §c⚠ Bugs may occur in this development build!"); + } else if (comparison < 0) { + // Running outdated version (current < latest) + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §c§lA new version is available!"); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §cCurrent version: §f" + currentVersion); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §cLatest version: §f" + latestVersion); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §eDownload from:"); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §bSpigot: §f" + spigotUrl); + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §bGitHub: §f" + GITHUB_URL); + } else { + // Versions are equal - running latest stable + plugin.getLogger().info("§8§l[§c§lLifeSteal§8§l] §a§lYou are running the latest stable version!"); + plugin.getLogger().info("§8§l[§c§lLifeSteal§8§l] §aCurrent version: §f" + currentVersion); + } + }); + } catch (Exception e) { + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §cFailed to check for updates: " + e.getMessage()); + } + }); + } + + private int compareVersions(String version1, String version2) { + try { + String[] v1Parts = version1.split("\\."); + String[] v2Parts = version2.split("\\."); + + // Convert to integers for proper comparison + int major1 = Integer.parseInt(v1Parts[0]); + int minor1 = v1Parts.length > 1 ? Integer.parseInt(v1Parts[1]) : 0; + int patch1 = v1Parts.length > 2 ? Integer.parseInt(v1Parts[2]) : 0; + + int major2 = Integer.parseInt(v2Parts[0]); + int minor2 = v2Parts.length > 1 ? Integer.parseInt(v2Parts[1]) : 0; + int patch2 = v2Parts.length > 2 ? Integer.parseInt(v2Parts[2]) : 0; + + // Compare major version first + if (major1 != major2) { + return major1 - major2; + } + + // If major versions are equal, compare minor versions + if (minor1 != minor2) { + return minor1 - minor2; + } + + // If minor versions are equal, compare patch versions + return patch1 - patch2; + + } catch (Exception e) { + plugin.getLogger().warning("§8§l[§c§lLifeSteal§8§l] §cError parsing version numbers!"); + return 0; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koopa/lifestealcore/utils/VersionSupport.java b/src/main/java/com/koopa/lifestealcore/utils/VersionSupport.java new file mode 100644 index 0000000..d94b988 --- /dev/null +++ b/src/main/java/com/koopa/lifestealcore/utils/VersionSupport.java @@ -0,0 +1,36 @@ +package com.koopa.lifestealcore.utils; + +import org.bukkit.Bukkit; + +public class VersionSupport { + private static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; + private static final int MAJOR_VERSION = Integer.parseInt(VERSION.split("_")[1]); + + public static boolean isLegacy() { + return MAJOR_VERSION < 13; + } + + public static boolean hasHexColors() { + return MAJOR_VERSION >= 16; + } + + public static boolean hasOffhand() { + return MAJOR_VERSION >= 9; + } + + public static boolean hasNewMaterials() { + return MAJOR_VERSION >= 13; + } + + public static String getServerVersion() { + return VERSION; + } + + public static int getMajorVersion() { + return MAJOR_VERSION; + } + + public static boolean isSupported() { + return MAJOR_VERSION >= 13; // Plugin supports 1.13+ + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..1257872 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,61 @@ +settings: + default-hearts: 10 + min-hearts: 1 + max-hearts: 20 + heart-item-name: "&c❤ Heart" + heart-item-lore: + - "&7Right-click to consume this heart" + - "&7and increase your max health" + difficulty: "MEDIUM" + recipe-materials: + EASY: + top_corners: "NETHER_STAR" + center_row: "IRON_INGOT" + skull: "SKELETON_SKULL" + bottom: "OBSIDIAN" + MEDIUM: + top_corners: "GLASS" + center_row: "DIAMOND" + skull: "WITHER_SKELETON_SKULL" + bottom: "OBSIDIAN" + HARD: + top_corners: "GLASS" + center_row: "NETHERITE_INGOT" + skull: "WITHER_SKELETON_SKULL" + bottom: "CRYING_OBSIDIAN" + CUSTOM: + top_corners: "GLASS" + center_row: "DIAMOND" + skull: "WITHER_SKELETON_SKULL" + bottom: "OBSIDIAN" +messages: + prefix: "&8[&cLifeSteal&8] " + no-permission: "&cYou don't have permission to do this!" + hearts-withdrawn: "&aYou have withdrawn &c%amount% &ahearts!" + hearts-deposited: "&aYou have deposited &c%amount% &ahearts!" + hearts-reset: "&aAll players' hearts have been reset to default!" + hearts-reset-confirm: "&cAre you sure you want to reset all hearts? Type /lifesteal reset confirm" + hearts-given: "&aGave &c%amount% &ahearts to &e%player%" + not-enough-hearts: "&cYou don't have enough hearts to withdraw!" + minimum-hearts-reached: "&cYou can't have less than %min% hearts!" + maximum-hearts-reached: "&cYou can't have more than %max% hearts!" + help: + header: "&8&l&m-----&r &c&lLifeSteal Help &8&l&m-----" + format: "&c/%command% &8- &7%description%" + footer: "&8&l&m-------------------------" + commands: + - command: "lifesteal help" + description: "Shows this help menu" + permission: "lifesteal.use" + - command: "lifesteal withdraw " + description: "Withdraw hearts into physical items" + permission: "lifesteal.withdraw" + - command: "lifesteal deposit" + description: "Deposit all heart items in your inventory" + permission: "lifesteal.deposit" + - command: "lifesteal reset" + description: "Reset all players' hearts to default" + permission: "lifesteal.admin.reset" + - command: "lifesteal giveheart " + description: "Give yourself heart items" + permission: "lifesteal.admin.give" \ No newline at end of file diff --git a/src/main/resources/hearts.yml b/src/main/resources/hearts.yml new file mode 100644 index 0000000..ef1922a --- /dev/null +++ b/src/main/resources/hearts.yml @@ -0,0 +1,2 @@ +# LifeStealCore hearts data storage +# Do not edit this file manually! \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..bb263ef --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,36 @@ +name: LifeStealCore +version: '1.0.0' +main: com.koopa.lifestealcore.LifeStealCore +api-version: '1.13' +author: Koopa +description: A LifeSteal plugin where players can steal hearts from others + +commands: + lifesteal: + description: Main command for LifeSteal plugin + usage: / + permission: lifesteal.use + aliases: [ls] + +permissions: + lifesteal.use: + description: Allows use of basic LifeSteal commands + default: true + lifesteal.withdraw: + description: Allows withdrawing hearts + default: true + lifesteal.deposit: + description: Allows depositing hearts + default: true + lifesteal.admin.config: + description: Allows access to the configuration GUI + default: op + lifesteal.admin.reset: + description: Allows resetting all players' hearts + default: op + lifesteal.admin.give: + description: Allows giving heart items + default: op + lifesteal.admin.debug: + description: Allows access to debug commands + default: op \ No newline at end of file