First Version.
This commit is contained in:
BigPappa committed 2026-08-08 17:39:22 -05:00
commit 0ed8e725e7
11 files changed
+1009

No files matched your search

@@ -0,0 +1,37 @@
package com.rsscripting.rsdisenchanter;
import com.rsscripting.rsdisenchanter.command.DisenchantCommand;
import com.rsscripting.rsdisenchanter.language.LanguageManager;
import com.rsscripting.rsdisenchanter.listener.DisenchanterListener;
import org.bukkit.plugin.java.JavaPlugin;
public final class RSDISenchanter extends JavaPlugin {
private LanguageManager languageManager;
@Override
public void onEnable() {
saveDefaultConfig();
languageManager = new LanguageManager(this);
languageManager.load();
getCommand("disenchant").setExecutor(new DisenchantCommand(this));
getServer().getPluginManager().registerEvents(
new DisenchanterListener(this),
this
);
getLogger().info("RS-DISenchanter enabled.");
}
@Override
public void onDisable() {
getLogger().info("RS-DISenchanter disabled.");
}
public LanguageManager getLanguageManager() {
return languageManager;
}
}
@@ -0,0 +1,43 @@
package com.rsscripting.rsdisenchanter.command;
import com.rsscripting.rsdisenchanter.RSDISenchanter;
import com.rsscripting.rsdisenchanter.gui.DisenchanterGUI;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class DisenchantCommand implements CommandExecutor {
private final RSDISenchanter plugin;
public DisenchantCommand(RSDISenchanter plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(
CommandSender sender,
Command command,
String label,
String[] args
) {
if (!(sender instanceof Player player)) {
sender.sendMessage(
plugin.getLanguageManager().get("messages.general.player-only")
);
return true;
}
if (!player.hasPermission("rsdisenchanter.use")) {
player.sendMessage(
plugin.getLanguageManager().get("messages.general.no-permission")
);
return true;
}
new DisenchanterGUI(plugin).open(player);
return true;
}
}
@@ -0,0 +1,101 @@
package com.rsscripting.rsdisenchanter.enchantment;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import java.util.LinkedHashMap;
import java.util.Map;
public class EnchantmentManager {
/**
* Gets all enchantments on an item.
*/
public Map<Enchantment, Integer> getEnchantments(ItemStack item) {
Map<Enchantment, Integer> enchantments = new LinkedHashMap<>();
if (item == null) {
return enchantments;
}
if (item.getItemMeta() instanceof EnchantmentStorageMeta bookMeta) {
enchantments.putAll(bookMeta.getStoredEnchants());
} else {
enchantments.putAll(item.getEnchantments());
}
return enchantments;
}
/**
* Gets only cursed enchantments on an item.
*/
public Map<Enchantment, Integer> getCurses(ItemStack item) {
Map<Enchantment, Integer> curses = new LinkedHashMap<>();
for (Map.Entry<Enchantment, Integer> entry :
getEnchantments(item).entrySet()) {
if (entry.getKey().isCursed()) {
curses.put(entry.getKey(), entry.getValue());
}
}
return curses;
}
/**
* Gets all non-cursed enchantments on an item.
*/
public Map<Enchantment, Integer> getNormalEnchantments(ItemStack item) {
Map<Enchantment, Integer> enchantments = new LinkedHashMap<>();
for (Map.Entry<Enchantment, Integer> entry :
getEnchantments(item).entrySet()) {
if (!entry.getKey().isCursed()) {
enchantments.put(entry.getKey(), entry.getValue());
}
}
return enchantments;
}
/**
* Creates a standard Minecraft enchanted book.
*/
public ItemStack createEnchantedBook(
Enchantment enchantment,
int level
) {
ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
EnchantmentStorageMeta meta =
(EnchantmentStorageMeta) book.getItemMeta();
if (meta != null) {
meta.addStoredEnchant(enchantment, level, true);
book.setItemMeta(meta);
}
return book;
}
/**
* Determines which enchantments should currently be displayed.
*
* Curses always take priority. Normal enchantments are displayed
* only after all curses have been removed.
*/
public Map<Enchantment, Integer> getDisplayEnchantments(ItemStack item) {
Map<Enchantment, Integer> curses = getCurses(item);
if (!curses.isEmpty()) {
return curses;
}
return getNormalEnchantments(item);
}
}
@@ -0,0 +1,131 @@
package com.rsscripting.rsdisenchanter.gui;
import com.rsscripting.rsdisenchanter.RSDISenchanter;
import com.rsscripting.rsdisenchanter.enchantment.EnchantmentManager;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashMap;
import java.util.HashMap;
import java.util.Map;
public class DisenchanterGUI implements InventoryHolder {
private final RSDISenchanter plugin;
private final Inventory inventory;
private final Map<Enchantment, Integer> removedCurseBooks = new HashMap<>();
public DisenchanterGUI(RSDISenchanter plugin) {
this.plugin = plugin;
String title = plugin.getLanguageManager()
.get("gui.titles.disenchanter");
inventory = Bukkit.createInventory(this, 27, title);
setupInventory();
}
private void setupInventory() {
ItemStack glass = createGlassPane();
// Left 3x3 border
inventory.setItem(0, glass);
inventory.setItem(1, glass);
inventory.setItem(2, glass);
inventory.setItem(9, glass);
inventory.setItem(11, glass);
inventory.setItem(18, glass);
inventory.setItem(19, glass);
inventory.setItem(20, glass);
}
private ItemStack createGlassPane() {
ItemStack item = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(
plugin.getLanguageManager()
.get("items.gui.glass-pane.name")
);
item.setItemMeta(meta);
}
return item;
}
public void open(Player player) {
player.openInventory(inventory);
}
public RSDISenchanter getPlugin() {
return plugin;
}
@Override
public Inventory getInventory() {
return inventory;
}
public void displayEnchantments(
Map<Enchantment, Integer> enchantments,
EnchantmentManager enchantmentManager
) {
clearBookSlots();
int[] bookSlots = {
3, 4, 5, 6, 7, 8,
12, 13, 14, 15, 16, 17,
21, 22, 23, 24, 25, 26
};
int slotIndex = 0;
for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
if (slotIndex >= bookSlots.length) {
break;
}
ItemStack book = enchantmentManager.createEnchantedBook(
entry.getKey(),
entry.getValue()
);
inventory.setItem(bookSlots[slotIndex], book);
slotIndex++;
}
}
public void clearBookSlots() {
int[] bookSlots = {
3, 4, 5, 6, 7, 8,
12, 13, 14, 15, 16, 17,
21, 22, 23, 24, 25, 26
};
for (int slot : bookSlots) {
inventory.setItem(slot, null);
}
}
public void recordRemovedCurse(Enchantment enchantment) {
removedCurseBooks.merge(enchantment, 1, Integer::sum);
}
public Map<Enchantment, Integer> getRemovedCurseBooks() {
return new HashMap<>(removedCurseBooks);
}
}
@@ -0,0 +1,72 @@
package com.rsscripting.rsdisenchanter.language;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.Map;
public class LanguageManager {
private final JavaPlugin plugin;
private FileConfiguration language;
public LanguageManager(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
String languageName = plugin.getConfig().getString("language", "en_us");
File languageFolder = new File(plugin.getDataFolder(), "language");
if (!languageFolder.exists() && !languageFolder.mkdirs()) {
plugin.getLogger().warning("Could not create language folder.");
}
File languageFile = new File(languageFolder, languageName + ".yml");
if (!languageFile.exists()) {
plugin.saveResource("language/" + languageName + ".yml", false);
}
language = YamlConfiguration.loadConfiguration(languageFile);
}
public String get(String path) {
if (language == null) {
return "";
}
String message = language.getString(path, "");
String prefix = language.getString("prefix", "");
message = message.replace("{prefix}", prefix);
return color(message);
}
public String get(String path, Map<String, String> variables) {
String message = get(path);
for (Map.Entry<String, String> entry : variables.entrySet()) {
message = message.replace(
"{" + entry.getKey() + "}",
entry.getValue()
);
}
return message;
}
public String getEnchantmentName(Enchantment enchantment) {
return get("enchantments." + enchantment.getKey().getKey());
}
private String color(String message) {
return ChatColor.translateAlternateColorCodes('&', message);
}
}
@@ -0,0 +1,386 @@
package com.rsscripting.rsdisenchanter.listener;
import com.rsscripting.rsdisenchanter.RSDISenchanter;
import com.rsscripting.rsdisenchanter.enchantment.EnchantmentManager;
import com.rsscripting.rsdisenchanter.gui.DisenchanterGUI;
import org.bukkit.Bukkit;
import org.bukkit.enchantments.Enchantment;
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.inventory.InventoryDragEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import java.util.Map;
public class DisenchanterListener implements Listener {
private final RSDISenchanter plugin;
private final EnchantmentManager enchantmentManager;
public DisenchanterListener(RSDISenchanter plugin) {
this.plugin = plugin;
this.enchantmentManager = new EnchantmentManager();
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getInventory().getHolder() instanceof DisenchanterGUI gui)) {
return;
}
if (event.isShiftClick()) {
event.setCancelled(true);
return;
}
int rawSlot = event.getRawSlot();
// Player inventory.
if (rawSlot >= event.getView().getTopInventory().getSize()) {
return;
}
// Center input slot.
if (rawSlot == 10) {
if (event.getWhoClicked() instanceof Player player) {
Bukkit.getScheduler().runTask(
plugin,
() -> validateInput(player, gui)
);
}
return;
}
if (isBookSlot(rawSlot)) {
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
ItemStack book = event.getCurrentItem();
if (book == null || book.getType().isAir()) {
return;
}
handleBookClick(player, gui, book, rawSlot);
return;
}
// All other GUI slots are protected.
event.setCancelled(true);
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
if (!(event.getInventory().getHolder() instanceof DisenchanterGUI)) {
return;
}
if (!(event.getPlayer() instanceof Player player)) {
return;
}
ItemStack item = event.getInventory().getItem(10);
if (item == null || item.getType().isAir()) {
return;
}
event.getInventory().setItem(10, null);
Map<Integer, ItemStack> leftovers = player.getInventory().addItem(item);
for (ItemStack leftover : leftovers.values()) {
player.getWorld().dropItemNaturally(
player.getLocation(),
leftover
);
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getInventory().getHolder() instanceof DisenchanterGUI)) {
return;
}
for (int slot : event.getRawSlots()) {
if (slot != 10) {
event.setCancelled(true);
return;
}
}
}
private void validateInput(Player player, DisenchanterGUI gui) {
ItemStack item = gui.getInventory().getItem(10);
if (item == null || item.getType().isAir()) {
gui.clearBookSlots();
return;
}
if (enchantmentManager.getEnchantments(item).isEmpty()) {
gui.clearBookSlots();
gui.getInventory().setItem(10, null);
player.getInventory().addItem(item);
player.sendMessage(
plugin.getLanguageManager()
.get("messages.general.no-enchantments")
);
return;
}
gui.displayEnchantments(
enchantmentManager.getDisplayEnchantments(item),
enchantmentManager
);
}
private boolean isBookSlot(int slot) {
return slot == 3 || slot == 4 || slot == 5
|| slot == 6 || slot == 7 || slot == 8
|| slot == 12 || slot == 13 || slot == 14
|| slot == 15 || slot == 16 || slot == 17
|| slot == 21 || slot == 22 || slot == 23
|| slot == 24 || slot == 25 || slot == 26;
}
private void handleBookClick(
Player player,
DisenchanterGUI gui,
ItemStack book,
int bookSlot
) {
if (!(book.getItemMeta() instanceof EnchantmentStorageMeta meta)) {
return;
}
Map<Enchantment, Integer> storedEnchantments =
meta.getStoredEnchants();
if (storedEnchantments.size() != 1) {
return;
}
Map.Entry<Enchantment, Integer> entry =
storedEnchantments.entrySet().iterator().next();
Enchantment enchantment = entry.getKey();
int level = entry.getValue();
ItemStack item = gui.getInventory().getItem(10);
if (item == null || item.getType().isAir()) {
return;
}
if (!enchantmentManager.getEnchantments(item)
.containsKey(enchantment)) {
return;
}
if (enchantment.isCursed()) {
handleCurseClick(player, gui, item, enchantment, level, bookSlot);
return;
}
ItemStack resultBook = enchantmentManager.createEnchantedBook(
enchantment,
level
);
Map<Integer, ItemStack> leftovers =
player.getInventory().addItem(resultBook);
if (!leftovers.isEmpty()) {
player.sendMessage(
plugin.getLanguageManager()
.get("messages.inventory.full")
);
return;
}
item.removeEnchantment(enchantment);
gui.getInventory().setItem(10, item);
// Remove the book that was clicked.
gui.getInventory().setItem(bookSlot, null);
gui.displayEnchantments(
enchantmentManager.getDisplayEnchantments(item),
enchantmentManager
);
}
private void handleCurseClick(
Player player,
DisenchanterGUI gui,
ItemStack item,
Enchantment enchantment,
int level,
int bookSlot
) {
ItemStack resultBook = enchantmentManager.createEnchantedBook(
enchantment,
level
);
Map<Integer, ItemStack> leftovers =
player.getInventory().addItem(resultBook);
if (!leftovers.isEmpty()) {
player.sendMessage(
plugin.getLanguageManager()
.get("messages.inventory.full")
);
return;
}
double destructionChance =
plugin.getConfig().getDouble(
"curse-destruction-chance",
5.0
);
double roll = Math.random() * 100.0;
if (roll < destructionChance) {
destroyFailedCurseSession(player, gui, item, enchantment);
return;
}
// Successful curse removal.
item.removeEnchantment(enchantment);
gui.getInventory().setItem(10, item);
gui.getInventory().setItem(bookSlot, null);
gui.recordRemovedCurse(enchantment);
String message = plugin.getLanguageManager()
.get("messages.curse.removed")
.replace(
"{enchantment}",
plugin.getLanguageManager()
.getEnchantmentName(enchantment)
)
.replace("{item}", getItemName(item));
player.sendMessage(message);
gui.displayEnchantments(
enchantmentManager.getDisplayEnchantments(item),
enchantmentManager
);
}
private void destroyFailedCurseSession(
Player player,
DisenchanterGUI gui,
ItemStack item,
Enchantment failedCurse
) {
removeSessionCurseBooks(
player,
gui.getRemovedCurseBooks()
);
gui.getInventory().setItem(10, null);
gui.clearBookSlots();
String message = plugin.getLanguageManager()
.get("messages.curse.failed")
.replace(
"{enchantment}",
plugin.getLanguageManager()
.getEnchantmentName(failedCurse)
)
.replace(
"{item}",
getItemName(item)
);
player.sendMessage(message);
player.closeInventory();
}
private void removeSessionCurseBooks(
Player player,
Map<Enchantment, Integer> booksToRemove
) {
for (Map.Entry<Enchantment, Integer> entry :
booksToRemove.entrySet()) {
Enchantment enchantment = entry.getKey();
int amountToRemove = entry.getValue();
for (ItemStack inventoryItem :
player.getInventory().getContents()) {
if (amountToRemove <= 0) {
break;
}
if (inventoryItem == null) {
continue;
}
if (!(inventoryItem.getItemMeta()
instanceof EnchantmentStorageMeta meta)) {
continue;
}
Map<Enchantment, Integer> stored =
meta.getStoredEnchants();
if (stored.size() != 1
|| !stored.containsKey(enchantment)) {
continue;
}
int removeAmount = Math.min(
amountToRemove,
inventoryItem.getAmount()
);
inventoryItem.setAmount(
inventoryItem.getAmount() - removeAmount
);
amountToRemove -= removeAmount;
}
}
}
private String getItemName(ItemStack item) {
if (item.hasItemMeta()
&& item.getItemMeta().hasDisplayName()) {
return item.getItemMeta().getDisplayName();
}
String name = item.getType().name()
.toLowerCase()
.replace('_', ' ');
return Character.toUpperCase(name.charAt(0))
+ name.substring(1);
}
}
+15
View File
@@ -0,0 +1,15 @@
# ==========================================
# RS-DISenchanter Configuration
# ==========================================
# Language file to use.
# The file must exist in the language folder.
language: en_us
# Chance, in percent, that an item is destroyed
# when attempting to remove a curse.
#
# 0 = Never destroyed
# 5 = 5% chance
# 100 = Always destroyed
curse-destruction-chance: 5
+86
View File
@@ -0,0 +1,86 @@
# ==========================================
# RS-DISenchanter - English Language File
# ==========================================
gui:
titles:
disenchanter: "&8Disenchanter"
messages:
general:
no-permission: "{prefix}&cYou do not have permission to use the Disenchanter."
player-only: "{prefix}&cThis command can only be used by a player."
invalid-item: "{prefix}&cThat item cannot be used in the Disenchanter."
no-enchantments: "{prefix}&cThat item has no enchantments to remove."
curse:
removed: "{prefix}&aYou successfully removed {enchantment} from the {item}."
failed: "{prefix}&cBad luck! You failed to remove {enchantment} from the {item} and the {item} was destroyed."
disenchant:
completed: "{prefix}&aThe enchantment was successfully removed."
inventory:
full: "{prefix}&cYour inventory is full."
items:
gui:
glass-pane:
name: " "
errors:
language:
file-not-found: "{prefix}&cThe language file could not be found."
invalid-language: "{prefix}&cThe configured language file is invalid."
enchantments:
aqua_affinity: "Aqua Affinity"
bane_of_arthropods: "Bane of Arthropods"
binding_curse: "Curse of Binding"
blast_protection: "Blast Protection"
breach: "Breach"
channeling: "Channeling"
density: "Density"
depth_strider: "Depth Strider"
efficiency: "Efficiency"
feather_falling: "Feather Falling"
fire_aspect: "Fire Aspect"
fortune: "Fortune"
frost_walker: "Frost Walker"
impaling: "Impaling"
knockback: "Knockback"
looting: "Looting"
loyalty: "Loyalty"
luck_of_the_sea: "Luck of the Sea"
lure: "Lure"
mending: "Mending"
multishot: "Multishot"
piercing: "Piercing"
power: "Power"
projectile_protection: "Projectile Protection"
protection: "Protection"
punch: "Punch"
quick_charge: "Quick Charge"
respiration: "Respiration"
riptide: "Riptide"
sharpness: "Sharpness"
silk_touch: "Silk Touch"
smite: "Smite"
soul_speed: "Soul Speed"
sweeping_edge: "Sweeping Edge"
swift_sneak: "Swift Sneak"
thorns: "Thorns"
unbreaking: "Unbreaking"
vanishing_curse: "Curse of Vanishing"
wind_burst: "Wind Burst"
prefix: "&8[&dDisenchanter&8] "
+17
View File
@@ -0,0 +1,17 @@
name: RS-DISenchanter
version: ${project.version}
main: com.rsscripting.rsdisenchanter.RSDISenchanter
api-version: '1.21'
author: Big Pappa, Atlas
description: Enchantment/Cursed Item Removal Tool
commands:
disenchant:
description: Opens the Disenchanter
usage: /disenchant
permission: rsdisenchanter.use
permissions:
rsdisenchanter.use:
description: Allows the player to use the Disenchanter
default: true