Release 1.0.0

This commit is contained in:
Koopa 2024-12-30 16:42:13 -05:00
parent a3ced2007b
commit 725274453e
22 changed files with 1803 additions and 2 deletions

38
.gitignore vendored Normal file
View File

@ -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

View File

@ -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`

74
pom.xml Normal file
View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.koopa</groupId>
<artifactId>lifestealcore</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>LifeStealCore</name>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -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!");
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<UUID, Boolean> 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 <amount> &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 <amount> &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 <ban|unban> <player> &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 <amount>"));
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 <amount>"));
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 <ban|unban> <player>"));
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'"));
}
}
}

View File

@ -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<String> loreList = new ArrayList<>();
for (String line : lore) {
loreList.add(MessageUtils.color(line));
}
meta.setLore(loreList);
item.setItemMeta(meta);
return item;
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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);
}
}
}

View File

@ -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<UUID, String> editingPlayers = new HashMap<>();
private final HashMap<UUID, String> 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;
}
}

View File

@ -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
}
}

View File

@ -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<UUID, Boolean> bannedPlayers = new HashMap<>();
private final Map<UUID, Location> 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<String> getBannedPlayers() {
List<String> 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));
}
}
}

View File

@ -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<UUID, Integer> 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<UUID, Integer> 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());
}
}
}

View File

@ -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"));
}
}

View File

@ -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<Map<?, ?>> 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")));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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+
}
}

View File

@ -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 <amount>"
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 <amount>"
description: "Give yourself heart items"
permission: "lifesteal.admin.give"

View File

@ -0,0 +1,2 @@
# LifeStealCore hearts data storage
# Do not edit this file manually!

View File

@ -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: /<command> <help|withdraw|deposit|reset|giveheart|recipe|config>
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