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!
+
+
+
+## ✦ 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