diff --git a/Compatibility/pom.xml b/Compatibility/pom.xml
index b1ac25c1..4ab3993b 100644
--- a/Compatibility/pom.xml
+++ b/Compatibility/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../
diff --git a/Compatibility/src/com/songoda/core/compatibility/ClassMapping.java b/Compatibility/src/com/songoda/core/compatibility/ClassMapping.java
index 8022b6ab..1e190849 100644
--- a/Compatibility/src/com/songoda/core/compatibility/ClassMapping.java
+++ b/Compatibility/src/com/songoda/core/compatibility/ClassMapping.java
@@ -13,6 +13,7 @@ public enum ClassMapping {
BLOCK_POSITION("core", "BlockPosition"),
CHAT_MESSAGE_TYPE("network.chat", "ChatMessageType"),
CHUNK("world.level.chunk", "Chunk"),
+ ENCHANTMENT_MANAGER("world.item.enchantment", "EnchantmentManager"),
ENTITY("world.entity", "Entity"),
ENTITY_INSENTIENT("world.entity", "EntityInsentient"),
ENTITY_PLAYER("server.level", "EntityPlayer"),
@@ -40,6 +41,7 @@ public enum ClassMapping {
CRAFT_BLOCK_DATA("block.data", "CraftBlockData"),
CRAFT_CHUNK("CraftChunk"),
CRAFT_ENTITY("entity", "CraftEntity"),
+ CRAFT_ITEM_STACK("inventory", "CraftItemStack"),
CRAFT_PLAYER("entity", "CraftPlayer"),
CRAFT_WORLD("CraftWorld");
diff --git a/Core/pom.xml b/Core/pom.xml
index 65f01789..4ab5715e 100644
--- a/Core/pom.xml
+++ b/Core/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../
diff --git a/Core/src/main/java/com/songoda/core/SongodaCore.java b/Core/src/main/java/com/songoda/core/SongodaCore.java
index 5523b53c..b365bcc5 100644
--- a/Core/src/main/java/com/songoda/core/SongodaCore.java
+++ b/Core/src/main/java/com/songoda/core/SongodaCore.java
@@ -56,7 +56,7 @@ public class SongodaCore {
/**
* This has been added as of Rev 6
*/
- private final static String coreVersion = "2.5.4";
+ private final static String coreVersion = "2.5.5";
/**
* This is specific to the website api
diff --git a/Core/src/main/java/com/songoda/core/lootables/Lootables.java b/Core/src/main/java/com/songoda/core/lootables/Lootables.java
new file mode 100644
index 00000000..6813a822
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/Lootables.java
@@ -0,0 +1,23 @@
+package com.songoda.core.lootables;
+
+import com.songoda.core.lootables.loot.LootManager;
+
+public class Lootables {
+
+ private final String lootablesDir;
+
+ private final LootManager lootManager;
+
+ public Lootables(String lootablesDir) {
+ this.lootablesDir = lootablesDir;
+ this.lootManager = new LootManager(this);
+ }
+
+ public String getLootablesDir() {
+ return lootablesDir;
+ }
+
+ public LootManager getLootManager() {
+ return lootManager;
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/Modify.java b/Core/src/main/java/com/songoda/core/lootables/Modify.java
new file mode 100644
index 00000000..316c2f6a
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/Modify.java
@@ -0,0 +1,8 @@
+package com.songoda.core.lootables;
+
+import com.songoda.core.lootables.loot.Loot;
+
+public interface Modify {
+
+ Loot Modify(Loot loot);
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/AbstractGuiListEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/AbstractGuiListEditor.java
new file mode 100644
index 00000000..23b61d21
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/AbstractGuiListEditor.java
@@ -0,0 +1,78 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.gui.AnvilGui;
+import com.songoda.core.gui.Gui;
+import com.songoda.core.gui.GuiUtils;
+import com.songoda.core.utils.TextUtils;
+import com.songoda.core.lootables.loot.Loot;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractGuiListEditor extends Gui {
+
+ protected final Loot loot;
+ private final Gui returnGui;
+
+ public AbstractGuiListEditor(Loot loot ,Gui returnGui) {
+ super(1, returnGui);
+ this.returnGui = returnGui;
+ this.loot = loot;
+ setDefaultItem(null);
+ paint();
+ }
+
+ public void paint() {
+ List lore = getData() == null ? new ArrayList<>() : getData();
+ setButton(2, GuiUtils.createButtonItem(CompatibleMaterial.OAK_FENCE_GATE,
+ TextUtils.formatText("&cBack")),
+ (event) -> {
+ guiManager.showGUI(event.player, returnGui);
+ ((GuiLootEditor) returnGui).paint();
+ });
+ setButton(6, GuiUtils.createButtonItem(CompatibleMaterial.OAK_FENCE_GATE,
+ TextUtils.formatText("&cBack")),
+ (event) -> {
+ guiManager.showGUI(event.player, returnGui);
+ ((GuiLootEditor) returnGui).paint();
+ });
+ setButton(3, GuiUtils.createButtonItem(CompatibleMaterial.ARROW,
+ TextUtils.formatText("&aAdd new line")),
+ (event -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e -> {
+ String validated = validate(gui.getInputText());
+ if (validated != null) {
+ lore.add(validated);
+ updateData(lore.isEmpty() ? null : lore);
+ e.player.closeInventory();
+ paint();
+ }
+ }));
+ gui.setTitle("Enter a new line");
+ guiManager.showGUI(event.player, gui);
+ }));
+
+ setItem(4, GuiUtils.createButtonItem(CompatibleMaterial.WRITABLE_BOOK,
+ TextUtils.formatText("&9Lore:"),
+ lore.isEmpty()
+ ? TextUtils.formatText(Collections.singletonList("&cNo lore set..."))
+ : TextUtils.formatText(lore)));
+
+ setButton(5, GuiUtils.createButtonItem(CompatibleMaterial.ARROW,
+ TextUtils.formatText("&cRemove the last line")),
+ (event -> {
+ lore.remove(lore.size() - 1);
+ updateData(lore);
+ paint();
+ }));
+ }
+
+ protected abstract List getData();
+
+ protected abstract void updateData(List list);
+
+ protected abstract String validate(String line);
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEditor.java
new file mode 100644
index 00000000..d0fd0fbb
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEditor.java
@@ -0,0 +1,81 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.gui.Gui;
+import com.songoda.core.gui.GuiUtils;
+import com.songoda.core.lootables.loot.LootManager;
+import com.songoda.core.lootables.loot.Lootable;
+import org.bukkit.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GuiEditor extends Gui {
+
+ private final LootManager lootManager;
+
+ public GuiEditor(LootManager lootManager) {
+ super(6);
+ this.lootManager = lootManager;
+ setDefaultItem(null);
+ setTitle("Lootables Overview");
+
+ paint();
+ }
+
+ private void paint() {
+ if (inventory != null)
+ inventory.clear();
+ setActionForRange(0, 0, 5, 9, null);
+
+ List lootables = new ArrayList<>(lootManager.getRegisteredLootables().values());
+
+ double itemCount = lootables.size();
+ this.pages = (int) Math.max(1, Math.ceil(itemCount / 36));
+
+ if (page != 1)
+ setButton(5, 2, GuiUtils.createButtonItem(CompatibleMaterial.ARROW, "Back"),
+ (event) -> {
+ page--;
+ paint();
+ });
+
+ if (page != pages)
+ setButton(5, 6, GuiUtils.createButtonItem(CompatibleMaterial.ARROW, "Next"),
+ (event) -> {
+ page++;
+ paint();
+ });
+
+ for (int i = 9; i < 45; i++) {
+ int current = ((page - 1) * 36) - 9;
+ if (current + i >= lootables.size()) {
+ setItem(i, null);
+ continue;
+ }
+ Lootable lootable = lootables.get(current + i);
+ if (lootable == null) continue;
+
+ setButton(i, getIcon(lootable.getKey()),
+ (event) -> guiManager.showGUI(event.player, new GuiLootableEditor(lootManager, lootable, this)));
+ }
+ }
+
+ public ItemStack getIcon(String key) {
+ ItemStack stack = null;
+ EntityType type = EntityType.fromName(key);
+ if (type != null) {
+ CompatibleMaterial material = CompatibleMaterial.getSpawnEgg(type);
+ if (material != null)
+ stack = material.getItem();
+ }
+ if (stack == null)
+ stack = CompatibleMaterial.GHAST_SPAWN_EGG.getItem();
+ ItemMeta meta = stack.getItemMeta();
+ meta.setDisplayName(key);
+ stack.setItemMeta(meta);
+ return stack;
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiEnchantEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEnchantEditor.java
new file mode 100644
index 00000000..aaae930b
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEnchantEditor.java
@@ -0,0 +1,96 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.gui.AnvilGui;
+import com.songoda.core.gui.Gui;
+import com.songoda.core.gui.GuiUtils;
+import com.songoda.core.lootables.loot.Loot;
+import com.songoda.core.utils.TextUtils;
+import com.songoda.core.lootables.loot.Loot;
+import org.bukkit.enchantments.Enchantment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class GuiEnchantEditor extends Gui {
+
+ private final Gui returnGui;
+ private final Loot loot;
+
+ public GuiEnchantEditor(Loot loot, Gui returnGui) {
+ super(1, returnGui);
+ this.returnGui = returnGui;
+ this.loot = loot;
+ setDefaultItem(null);
+ setTitle("Enchantment Editor");
+ paint();
+ }
+
+ public void paint() {
+ Map lore = loot.getEnchants() == null ? new HashMap<>() : new HashMap<>(loot.getEnchants());
+ setButton(2, GuiUtils.createButtonItem(CompatibleMaterial.OAK_FENCE_GATE,
+ TextUtils.formatText("&cBack")),
+ (event) -> {
+ guiManager.showGUI(event.player, returnGui);
+ ((GuiLootEditor) returnGui).paint();
+ });
+ setButton(6, GuiUtils.createButtonItem(CompatibleMaterial.OAK_FENCE_GATE,
+ TextUtils.formatText("&cBack")),
+ (event) -> {
+ guiManager.showGUI(event.player, returnGui);
+ ((GuiLootEditor) returnGui).paint();
+ });
+ setButton(3, GuiUtils.createButtonItem(CompatibleMaterial.ARROW,
+ TextUtils.formatText("&aAdd new line")),
+ (event -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e -> {
+ if (Enchantment.getByName(gui.getInputText().toUpperCase().trim()) == null) {
+ e.player.sendMessage("That is not a valid enchantment.");
+ e.player.closeInventory();
+ return;
+ }
+
+ AnvilGui gui1 = new AnvilGui(event.player, this);
+ gui1.setAction((ee -> {
+ lore.put(gui.getInputText().toUpperCase().trim(), Integer.parseInt(gui1.getInputText().trim()));
+ loot.setEnchants(lore.isEmpty() ? null : lore);
+ ee.player.closeInventory();
+ paint();
+ }));
+ gui1.setTitle("Enter a level");
+ guiManager.showGUI(event.player, gui1);
+ }));
+ gui.setTitle("Enter an enchant");
+ guiManager.showGUI(event.player, gui);
+ }));
+
+ List enchantments = new ArrayList<>();
+
+ String last = null;
+
+ if (!lore.isEmpty())
+ for (Map.Entry entry : lore.entrySet()) {
+ last = entry.getKey();
+ enchantments.add("&6" + entry.getKey() + " " + entry.getValue());
+ }
+
+ setItem(4, GuiUtils.createButtonItem(CompatibleMaterial.WRITABLE_BOOK,
+ TextUtils.formatText("&7Enchant Override:"),
+ lore.isEmpty()
+ ? TextUtils.formatText(Collections.singletonList("&cNo enchantments set..."))
+ : TextUtils.formatText(enchantments)));
+
+ String lastFinal = last;
+ setButton(5, GuiUtils.createButtonItem(CompatibleMaterial.ARROW,
+ TextUtils.formatText("&cRemove the last line")),
+ (event -> {
+ lore.remove(lastFinal);
+ loot.setEnchants(lore);
+ paint();
+ }));
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiEntityEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEntityEditor.java
new file mode 100644
index 00000000..56e2faa2
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiEntityEditor.java
@@ -0,0 +1,36 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.gui.Gui;
+import com.songoda.core.lootables.loot.Loot;
+import org.bukkit.entity.EntityType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class GuiEntityEditor extends AbstractGuiListEditor {
+
+ public GuiEntityEditor(Loot loot, Gui returnGui) {
+ super(loot, returnGui);
+ }
+
+ @Override
+ protected List getData() {
+ return loot.getOnlyDropFor().stream().map(Enum::name).collect(Collectors.toList());
+ }
+
+ @Override
+ protected void updateData(List list) {
+ loot.setOnlyDropFor(list.stream().map(EntityType::valueOf).collect(Collectors.toList()));
+ }
+
+ @Override
+ protected String validate(String line) {
+ line = line.toUpperCase().trim();
+ try {
+ EntityType.valueOf(line);
+ return line;
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootEditor.java
new file mode 100644
index 00000000..8e2d0228
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootEditor.java
@@ -0,0 +1,268 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.gui.AnvilGui;
+import com.songoda.core.gui.Gui;
+import com.songoda.core.gui.GuiUtils;
+import com.songoda.core.utils.TextUtils;
+import com.songoda.core.lootables.loot.Loot;
+import com.songoda.core.lootables.loot.LootBuilder;
+import com.songoda.core.lootables.loot.LootManager;
+import org.bukkit.entity.EntityType;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class GuiLootEditor extends Gui {
+
+ private final LootManager lootManager;
+ private final Loot loot;
+ private final Gui returnGui;
+
+ public GuiLootEditor(LootManager lootManager, Loot loot, Gui returnGui) {
+ super(6, returnGui);
+ this.lootManager = lootManager;
+ this.loot = loot;
+ this.returnGui = returnGui;
+ setDefaultItem(null);
+ setTitle("Loot Editor");
+ paint();
+ setOnClose((event) ->
+ lootManager.saveLootables(false));
+ }
+
+ public void paint() {
+ if (inventory != null)
+ inventory.clear();
+ setActionForRange(0, 0, 5, 9, null);
+
+ setButton(8, GuiUtils.createButtonItem(CompatibleMaterial.OAK_DOOR,
+ TextUtils.formatText("&cBack")),
+ (event) -> {
+ guiManager.showGUI(event.player, returnGui);
+ });
+
+ setButton(9, GuiUtils.createButtonItem(loot.getMaterial() == null ? CompatibleMaterial.BARRIER : loot.getMaterial(),
+ TextUtils.formatText("&7Current Material: &6" + (loot.getMaterial() != null
+ ? loot.getMaterial().name() : "None")), TextUtils.formatText(
+ Arrays.asList("",
+ "&8Click to set the material to",
+ "&8the material in your hand.")
+ )), (event) -> {
+ ItemStack stack = event.player.getInventory().getItemInMainHand();
+ loot.setMaterial(CompatibleMaterial.getMaterial(stack));
+ paint();
+ });
+
+ setButton(10, GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ TextUtils.formatText("&7Name Override: &6" + (loot.getName() == null ? "None set" : loot.getName()))),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e -> {
+ loot.setName(gui.getInputText().trim());
+ paint();
+ e.player.closeInventory();
+ }));
+ guiManager.showGUI(event.player, gui);
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER, loot.getName()));
+ });
+
+ setButton(11, GuiUtils.createButtonItem(CompatibleMaterial.WRITABLE_BOOK,
+ TextUtils.formatText("&7Lore Override:"),
+ TextUtils.formatText(loot.getLore() == null ? Collections.singletonList("&6None set") : loot.getLore())),
+ (event) -> guiManager.showGUI(event.player, new GuiLoreEditor(loot, this)));
+
+ List enchantments = new ArrayList<>();
+
+ if (loot.getEnchants() != null)
+ for (Map.Entry entry : loot.getEnchants().entrySet())
+ enchantments.add("&6" + entry.getKey() + " " + entry.getValue());
+
+ setButton(12, GuiUtils.createButtonItem(CompatibleMaterial.ENCHANTED_BOOK,
+ TextUtils.formatText("&7Enchantments:"),
+ TextUtils.formatText(enchantments.isEmpty() ? Collections.singletonList("&6None set") : enchantments)),
+ (event) -> guiManager.showGUI(event.player, new GuiEnchantEditor(loot, this)));
+
+ setButton(13, GuiUtils.createButtonItem(
+ loot.getBurnedMaterial() == null
+ ? CompatibleMaterial.FIRE_CHARGE
+ : loot.getBurnedMaterial(),
+ TextUtils.formatText("&7Current Burned Material: &6"
+ + (loot.getBurnedMaterial() == null
+ ? "None"
+ : loot.getBurnedMaterial().name())), TextUtils.formatText(
+ Arrays.asList("",
+ "&8Click to set the burned material to",
+ "&8the material in your hand.")
+ )),
+ (event) -> {
+ ItemStack stack = event.player.getInventory().getItemInMainHand();
+ loot.setBurnedMaterial(CompatibleMaterial.getMaterial(stack));
+ paint();
+ });
+
+ setButton(14, GuiUtils.createButtonItem(CompatibleMaterial.CLOCK,
+ TextUtils.formatText("&7Chance: &6" + loot.getChance()),
+ TextUtils.formatText(
+ Arrays.asList("",
+ "&8Click to edit this loots",
+ "&8drop chance.")
+ )),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setChance(Double.parseDouble(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getChance())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(15, GuiUtils.createButtonItem(CompatibleMaterial.REDSTONE,
+ TextUtils.formatText("&7Min Drop Amount: &6" + loot.getMin())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setMin(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getMin())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(16, GuiUtils.createButtonItem(CompatibleMaterial.GLOWSTONE_DUST,
+ TextUtils.formatText("&7Max Drop Amount: &6" + loot.getMax())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setMax(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getMax())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(17, GuiUtils.createButtonItem(CompatibleMaterial.REDSTONE,
+ TextUtils.formatText("&7Min Item Damage: &6" + loot.getDamageMin())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setDamageMin(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getDamageMin())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(18, GuiUtils.createButtonItem(CompatibleMaterial.GLOWSTONE_DUST,
+ TextUtils.formatText("&7Max Item Damage: &6" + loot.getDamageMax())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setDamageMax(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getDamageMax())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(19, GuiUtils.createButtonItem(CompatibleMaterial.CHEST,
+ TextUtils.formatText("&7Allow Looting Enchantment?: &6" + loot.isAllowLootingEnchant())),
+ (event) -> {
+ loot.setAllowLootingEnchant(!loot.isAllowLootingEnchant());
+ paint();
+ event.player.closeInventory();
+ });
+
+ setButton(20, GuiUtils.createButtonItem(CompatibleMaterial.REDSTONE,
+ TextUtils.formatText("&7Min Child Loot Min: &6" + loot.getChildDropCountMin())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setChildDropCountMin(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getChildDropCountMin())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ setButton(21, GuiUtils.createButtonItem(CompatibleMaterial.GLOWSTONE_DUST,
+ TextUtils.formatText("&7Min Child Loot Max: &6" + loot.getChildDropCountMax())),
+ (event) -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((e) -> {
+ loot.setChildDropCountMax(Integer.parseInt(gui.getInputText()));
+ paint();
+ e.player.closeInventory();
+ });
+ gui.setInput(GuiUtils.createButtonItem(CompatibleMaterial.PAPER,
+ String.valueOf(loot.getChildDropCountMax())));
+ guiManager.showGUI(event.player, gui);
+ });
+
+ List entities = new ArrayList<>();
+
+ if (loot.getOnlyDropFor() != null)
+ for (EntityType entity : loot.getOnlyDropFor())
+ entities.add("&6" + entity.name());
+
+ setButton(22, GuiUtils.createButtonItem(CompatibleMaterial.ENCHANTED_BOOK,
+ TextUtils.formatText("&7Only Drop For:"),
+ TextUtils.formatText(entities)),
+ (event) -> guiManager.showGUI(event.player, new GuiEntityEditor(loot, this)));
+
+ setButton(4, 0, GuiUtils.createButtonItem(CompatibleMaterial.LIME_DYE, TextUtils.formatText("&aCreate new Child Loot")),
+ (event -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((event1 -> {
+ try {
+ loot.addChildLoots(new LootBuilder().setMaterial(CompatibleMaterial
+ .valueOf(gui.getInputText().trim())).build());
+ } catch (IllegalArgumentException e) {
+ event.player.sendMessage("That is not a valid material.");
+ }
+ event.player.closeInventory();
+ paint();
+ }));
+ gui.setTitle("Enter a material");
+ guiManager.showGUI(event.player, gui);
+ }));
+
+ int i = 9 * 5;
+ for (Loot loot : loot.getChildLoot()) {
+ ItemStack item = loot.getMaterial() == null
+ ? CompatibleMaterial.BARRIER.getItem()
+ : GuiUtils.createButtonItem(loot.getMaterial(), null,
+ TextUtils.formatText("&6Left click &7to edit"),
+ TextUtils.formatText("&6Right click &7to destroy"));
+
+ setButton(i, item,
+ (event) -> {
+ if (event.clickType == ClickType.RIGHT) {
+ this.loot.removeChildLoot(loot);
+ paint();
+ } else if (event.clickType == ClickType.LEFT) {
+ guiManager.showGUI(event.player, new GuiLootEditor(lootManager, loot, this));
+ }
+ });
+ i++;
+ }
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootableEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootableEditor.java
new file mode 100644
index 00000000..69f44010
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLootableEditor.java
@@ -0,0 +1,78 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.gui.AnvilGui;
+import com.songoda.core.gui.Gui;
+import com.songoda.core.gui.GuiUtils;
+import com.songoda.core.utils.TextUtils;
+import com.songoda.core.lootables.loot.Loot;
+import com.songoda.core.lootables.loot.LootBuilder;
+import com.songoda.core.lootables.loot.LootManager;
+import com.songoda.core.lootables.loot.Lootable;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+public class GuiLootableEditor extends Gui {
+
+ private final LootManager lootManager;
+ private final Lootable lootable;
+ private final Gui returnGui;
+
+ public GuiLootableEditor(LootManager lootManager, Lootable lootable, Gui returnGui) {
+ super(6);
+ this.lootManager = lootManager;
+ this.lootable = lootable;
+ this.returnGui = returnGui;
+ setOnClose((event) ->
+ lootManager.saveLootables(false));
+ setDefaultItem(null);
+ setTitle("Lootables Editor");
+ paint();
+ }
+
+ private void paint() {
+ if (inventory != null)
+ inventory.clear();
+ setActionForRange(0, 0, 5, 9, null);
+
+ setButton(0, GuiUtils.createButtonItem(CompatibleMaterial.LIME_DYE, TextUtils.formatText("&aCreate new Loot")),
+ (event -> {
+ AnvilGui gui = new AnvilGui(event.player, this);
+ gui.setAction((event1 -> {
+ try {
+ lootable.registerLoot(new LootBuilder().setMaterial(CompatibleMaterial
+ .valueOf(gui.getInputText().trim())).build());
+ } catch (IllegalArgumentException e) {
+ event.player.sendMessage("That is not a valid material.");
+ }
+ event.player.closeInventory();
+ paint();
+ }));
+ gui.setTitle("Enter a material");
+ guiManager.showGUI(event.player, gui);
+ }));
+
+ setButton(8, GuiUtils.createButtonItem(CompatibleMaterial.OAK_DOOR, TextUtils.formatText("&cBack")),
+ (event -> guiManager.showGUI(event.player, returnGui)));
+
+ int i = 9;
+ for (Loot loot : lootable.getRegisteredLoot()) {
+ ItemStack item = loot.getMaterial() == null
+ ? CompatibleMaterial.BARRIER.getItem()
+ : GuiUtils.createButtonItem(loot.getMaterial(), null,
+ TextUtils.formatText("&6Left click &7to edit"),
+ TextUtils.formatText("&6Right click &7to destroy"));
+
+ setButton(i, item,
+ (event) -> {
+ if (event.clickType == ClickType.RIGHT) {
+ lootable.removeLoot(loot);
+ paint();
+ } else if (event.clickType == ClickType.LEFT) {
+ guiManager.showGUI(event.player, new GuiLootEditor(lootManager, loot, this));
+ }
+ });
+ i++;
+ }
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/gui/GuiLoreEditor.java b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLoreEditor.java
new file mode 100644
index 00000000..3a938bb4
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/gui/GuiLoreEditor.java
@@ -0,0 +1,28 @@
+package com.songoda.core.lootables.gui;
+
+import com.songoda.core.gui.Gui;
+import com.songoda.core.lootables.loot.Loot;
+
+import java.util.List;
+
+public class GuiLoreEditor extends AbstractGuiListEditor {
+
+ public GuiLoreEditor(Loot loot, Gui returnGui) {
+ super(loot, returnGui);
+ }
+
+ @Override
+ protected List getData() {
+ return loot.getLore();
+ }
+
+ @Override
+ protected void updateData(List list) {
+ loot.setLore(list);
+ }
+
+ @Override
+ protected String validate(String line) {
+ return line.trim();
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/Drop.java b/Core/src/main/java/com/songoda/core/lootables/loot/Drop.java
new file mode 100644
index 00000000..7a08f1fe
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/Drop.java
@@ -0,0 +1,48 @@
+package com.songoda.core.lootables.loot;
+
+import org.bukkit.inventory.ItemStack;
+
+public class Drop {
+
+ private ItemStack itemStack;
+
+ private String command;
+
+ private int xp;
+
+ public Drop(ItemStack itemStack) {
+ this.itemStack = itemStack;
+ }
+
+ public Drop(String command) {
+ this.command = command;
+ }
+
+ public Drop(int xp) {
+ this.xp = xp;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ public int getXp() {
+ return xp;
+ }
+
+ public void setXp(int xp) {
+ this.xp = xp;
+ }
+
+ public ItemStack getItemStack() {
+ return itemStack;
+ }
+
+ public void setItemStack(ItemStack itemStack) {
+ this.itemStack = itemStack;
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/DropUtils.java b/Core/src/main/java/com/songoda/core/lootables/loot/DropUtils.java
new file mode 100644
index 00000000..7bda0ccd
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/DropUtils.java
@@ -0,0 +1,69 @@
+package com.songoda.core.lootables.loot;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DropUtils {
+
+ public static void processStackedDrop(LivingEntity entity, List drops, EntityDeathEvent event) {
+ int xpToDrop = event.getDroppedExp();
+ List items = new ArrayList<>();
+ List commands = new ArrayList<>();
+ List xp = new ArrayList<>();
+ for (Drop drop : drops) {
+ if (drop == null) continue;
+
+ ItemStack droppedItem = drop.getItemStack();
+ if (droppedItem != null) {
+ droppedItem = droppedItem.clone();
+ boolean success = false;
+ for (ItemStack item : items) {
+ if (item.getType() != droppedItem.getType()
+ || item.getDurability() != droppedItem.getDurability()
+ || item.getAmount() + droppedItem.getAmount() > droppedItem.getMaxStackSize()) continue;
+ item.setAmount(item.getAmount() + droppedItem.getAmount());
+ success = true;
+ break;
+ }
+ if (!success)
+ items.add(droppedItem);
+ }
+ if (drop.getCommand() != null)
+ commands.add(drop.getCommand());
+
+ if (drop.getXp() != 0)
+ xp.add(drop.getXp());
+ }
+
+ event.getDrops().clear();
+
+ if (!items.isEmpty())
+ dropItems(items, event);
+ else if (!commands.isEmpty())
+ runCommands(entity, commands);
+
+ for (int x : xp)
+ xpToDrop += x;
+ event.setDroppedExp(xpToDrop);
+ }
+
+ private static void dropItems(List items, EntityDeathEvent event) {
+ for (ItemStack item : items)
+ event.getDrops().add(item);
+ }
+
+ private static void runCommands(LivingEntity entity, List commands) {
+ for (String command : commands) {
+ if (entity.getKiller() != null)
+ command = command.replace("%player%", entity.getKiller().getName());
+ if (!command.contains("%player%"))
+ Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
+ }
+ }
+
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/Loot.java b/Core/src/main/java/com/songoda/core/lootables/loot/Loot.java
new file mode 100644
index 00000000..21232cbc
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/Loot.java
@@ -0,0 +1,314 @@
+package com.songoda.core.lootables.loot;
+
+import com.google.gson.annotations.SerializedName;
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.utils.ItemUtils;
+import com.songoda.core.utils.TextUtils;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+public class Loot {
+
+ // Command ran for this drop.
+ @SerializedName("Command")
+ private String command;
+
+ // Xp for this drop.
+ @SerializedName("xp")
+ private int xp = 0;
+
+ // Material used for this drop.
+ @SerializedName("Type")
+ private CompatibleMaterial material;
+
+ // The override for the item name.
+ @SerializedName("Name")
+ private String name = null;
+
+ // The override for the item lore.
+ @SerializedName("Lore")
+ private List lore = null;
+
+ // The override for the item enchantments.
+ @SerializedName("Enchantments")
+ private Map enchants = null;
+
+ // Material used if entity died on fire.
+ @SerializedName("Burned Type")
+ private CompatibleMaterial burnedMaterial = null;
+
+ // Chance that this drop will take place.
+ @SerializedName("Chance")
+ private double chance = 100;
+
+ // Minimum amount of this item.
+ @SerializedName("Min")
+ private int min = 1;
+
+ // Maximum amount of this item.
+ @SerializedName("Max")
+ private int max = 1;
+
+ // The override for chances applied by the wield item.
+ @SerializedName("Wielded Enchantment Chance Overrides")
+ private Map enchantChances = null;
+
+ // Min amount of applied damage.
+ @SerializedName("Damage Min")
+ private Integer damageMin = null;
+
+ // Max amount of applied damage.
+ @SerializedName("Damage Max")
+ private Integer damageMax = null;
+
+ // Will the looting enchantment be usable for this loot?
+ @SerializedName("Looting")
+ private boolean allowLootingEnchant = true;
+
+ // The looting chance increase.
+ @SerializedName("Looting Chance Increase")
+ private Double lootingIncrease;
+
+ // Should this drop only be applicable for specific entities?
+ @SerializedName("Only Drop For")
+ private List onlyDropFor;
+
+ // How many child loots should drop?
+ @SerializedName("Child Loot Drop Count Min")
+ private Integer childDropCountMin;
+ @SerializedName("Child Loot Drop Count Max")
+ private Integer childDropCountMax;
+
+ // Should this drop house child drops?
+ @SerializedName("Child Loot")
+ private List childLoot;
+
+ // Should the entity be charged? (Only works on creepers)
+ private boolean requireCharged = false;
+
+ public CompatibleMaterial getMaterial() {
+ return material;
+ }
+
+ public void setMaterial(CompatibleMaterial material) {
+ this.material = material;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ public int getXp() {
+ return xp;
+ }
+
+ public void setXp(int xp) {
+ this.xp = xp;
+ }
+
+ public String getName() {
+ return TextUtils.formatText(name);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getLore() {
+ if (lore == null) return null;
+ List lore = new ArrayList<>();
+ for (String line : this.lore)
+ lore.add(TextUtils.formatText(line));
+
+ return lore;
+ }
+
+ public void setLore(List lore) {
+ this.lore = new ArrayList<>(lore);
+ }
+
+ public ItemStack getEnchants(ItemStack item) {
+ if (enchants == null) return null;
+ Map enchants = new HashMap<>();
+ for (Map.Entry entry : this.enchants.entrySet()) {
+
+ if (entry.getValue() == null) continue;
+
+ if (entry.getKey().equalsIgnoreCase("RANDOM")) {
+ item = ItemUtils.applyRandomEnchants(item, entry.getValue());
+ continue;
+ }
+ enchants.put(Enchantment.getByName(entry.getKey()), entry.getValue());
+ }
+ item.addEnchantments(enchants);
+ return item;
+ }
+
+ public void setEnchants(Map enchants) {
+ this.enchants = enchants;
+ }
+
+
+ public void setEnchantChances(Map enchants) {
+ this.enchantChances = enchants;
+ }
+
+ public Map getEnchants() {
+ return enchants == null ? null : Collections.unmodifiableMap(enchants);
+ }
+
+ public CompatibleMaterial getBurnedMaterial() {
+ return burnedMaterial;
+ }
+
+ public void setBurnedMaterial(CompatibleMaterial burnedMaterial) {
+ this.burnedMaterial = burnedMaterial;
+ }
+
+ public double getChance() {
+ return chance;
+ }
+
+ public void setChance(double chance) {
+ this.chance = chance;
+ }
+
+ public boolean runChance(int looting, ItemStack murderWeapon) {
+ double chance = this.chance;
+ if (enchantChances != null && murderWeapon != null && enchants != null) {
+ for (Map.Entry entry : murderWeapon.getEnchantments().entrySet()) {
+ String key = entry.getKey().getName() + ":" + entry.getValue();
+ if (!enchants.containsKey(key)) continue;
+ double ch = enchantChances.get(key);
+ if (ch > chance)
+ chance = enchantChances.get(key);
+ }
+ }
+ return (Math.random() * 100) - (chance + (lootingIncrease == null ? 1
+ : lootingIncrease * looting)) < 0 || chance == 100;
+ }
+
+ public int getMin() {
+ return min;
+ }
+
+ public void setMin(int min) {
+ this.min = min;
+ }
+
+ public int getMax() {
+ return max;
+ }
+
+ public void setMax(int max) {
+ this.max = max;
+ }
+
+ public int getDamageMax() {
+ return damageMax == null ? 0 : damageMax;
+ }
+
+ public void setDamageMax(int damageMax) {
+ this.damageMax = damageMax;
+ }
+
+ public int getDamageMin() {
+ return damageMin == null ? 0 : damageMin;
+ }
+
+ public void setDamageMin(int damageMin) {
+ this.damageMin = damageMin;
+ }
+
+ public int getAmountToDrop(int looting) {
+ return min == max ? (max + getLooting(looting)) : new Random().nextInt((max + getLooting(looting)) - min + 1) + min;
+ }
+
+ public int getLooting(int looting) {
+ return allowLootingEnchant ? looting : 0;
+ }
+
+ public boolean isAllowLootingEnchant() {
+ return allowLootingEnchant;
+ }
+
+ public void setAllowLootingEnchant(boolean allowLootingEnchant) {
+ this.allowLootingEnchant = allowLootingEnchant;
+ }
+
+ public void setLootingIncrease(double increase) {
+ this.lootingIncrease = increase;
+ }
+
+ public void addChildLoots(Loot... loots) {
+ this.childDropCountMin = 1;
+ this.childDropCountMax = 1;
+ if (childLoot == null)
+ this.childLoot = new ArrayList<>();
+ this.childLoot.addAll(Arrays.asList(loots));
+ }
+
+ public void removeChildLoot(Loot loot) {
+ if (childLoot == null) return;
+ this.childLoot.remove(loot);
+ }
+
+ public List getChildLoot() {
+ return childLoot == null ? new ArrayList<>() : new ArrayList<>(childLoot);
+ }
+
+ public List getOnlyDropFor() {
+ return onlyDropFor == null ? new ArrayList<>() : new ArrayList<>(onlyDropFor);
+ }
+
+ public void addOnlyDropFor(EntityType... types) {
+ this.onlyDropFor = new ArrayList<>();
+ this.onlyDropFor.addAll(Arrays.asList(types));
+ }
+
+ public void setOnlyDropFor(List types) {
+ this.onlyDropFor = types;
+ }
+
+ public void setChildDropCountMin(int childDropCountMin) {
+ this.childDropCountMin = childDropCountMin;
+ }
+
+ public void setChildDropCountMax(int childDropCountMax) {
+ this.childDropCountMax = childDropCountMax;
+ }
+
+ public Integer getChildDropCountMin() {
+ return childDropCountMin;
+ }
+
+ public Integer getChildDropCountMax() {
+ return childDropCountMax;
+ }
+
+ public int getChildDropCount() {
+ if (childDropCountMin == null || childDropCountMax == null) return 0;
+ return new Random().nextInt(childDropCountMax - childDropCountMin + 1) + childDropCountMin;
+ }
+
+ public boolean isRequireCharged() {
+ return requireCharged;
+ }
+
+ public void setRequireCharged(boolean requireCharged) {
+ this.requireCharged = requireCharged;
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/LootBuilder.java b/Core/src/main/java/com/songoda/core/lootables/loot/LootBuilder.java
new file mode 100644
index 00000000..bfaf5053
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/LootBuilder.java
@@ -0,0 +1,143 @@
+package com.songoda.core.lootables.loot;
+
+
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.lootables.loot.objects.EnchantChance;
+import org.bukkit.entity.EntityType;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class LootBuilder {
+
+ private final Loot loot;
+
+ public LootBuilder() {
+ this.loot = new Loot();
+ }
+
+ public LootBuilder setMaterial(CompatibleMaterial material) {
+ this.loot.setMaterial(material);
+ return this;
+ }
+
+ public LootBuilder setName(String name) {
+ this.loot.setName(name);
+ return this;
+ }
+
+ public LootBuilder addLore(String... lore) {
+ this.loot.setLore(Arrays.asList(lore));
+ return this;
+ }
+
+ public LootBuilder addEnchants(Tuple... tuples) {
+ Map enchants = new HashMap<>();
+ for (Tuple tuple : tuples)
+ enchants.put((String)tuple.getKey(), (int)tuple.getValue());
+ this.loot.setEnchants(enchants);
+ return this;
+ }
+
+ public LootBuilder addEnchantChances(EnchantChance... enchantChances) {
+ Map enchants = new HashMap<>();
+ for (EnchantChance chance : enchantChances)
+ enchants.put(chance.getEnchantment().getName() + ":" + chance.getLevel(), chance.getChanceOverride());
+ this.loot.setEnchantChances(enchants);
+ return this;
+ }
+
+ public LootBuilder setBurnedMaterial(CompatibleMaterial material) {
+ this.loot.setBurnedMaterial(material);
+ return this;
+ }
+
+ public LootBuilder setChance(double chance) {
+ this.loot.setChance(chance);
+ return this;
+ }
+
+ public LootBuilder setMin(int min) {
+ this.loot.setMin(min);
+ return this;
+ }
+
+ public LootBuilder setMax(int max) {
+ this.loot.setMax(max);
+ return this;
+ }
+
+ public LootBuilder setDamageMin(int min) {
+ this.loot.setDamageMin(min);
+ return this;
+ }
+
+ public LootBuilder setDamageMax(int max) {
+ this.loot.setDamageMax(max);
+ return this;
+ }
+
+ public LootBuilder setAllowLootingEnchant(boolean allow) {
+ this.loot.setAllowLootingEnchant(allow);
+ return this;
+ }
+
+ public LootBuilder setLootingIncrease(double increase) {
+ this.loot.setLootingIncrease(increase);
+ return this;
+ }
+
+ public LootBuilder addOnlyDropFors(EntityType... types) {
+ this.loot.addOnlyDropFor(types);
+ return this;
+ }
+
+ public LootBuilder addChildLoot(Loot... loots) {
+ this.loot.addChildLoots(loots);
+ return this;
+ }
+
+ public LootBuilder setChildDropCount(int count) {
+ this.loot.setChildDropCountMin(count);
+ this.loot.setChildDropCountMax(count);
+ return this;
+ }
+
+ public LootBuilder setChildDropCounMin(int count) {
+ this.loot.setChildDropCountMin(count);
+ return this;
+ }
+
+ public LootBuilder setChildDropCountMax(int count) {
+ this.loot.setChildDropCountMax(count);
+ return this;
+ }
+
+ public LootBuilder setRequireCharged(boolean require) {
+ this.loot.setRequireCharged(require);
+ return this;
+ }
+
+ public Loot build() {
+ return this.loot;
+ }
+
+ public static class Tuple {
+ public final key x;
+ public final value y;
+
+ public Tuple(key x, value y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public key getKey() {
+ return this.x;
+ }
+
+ public value getValue() {
+ return this.y;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/LootManager.java b/Core/src/main/java/com/songoda/core/lootables/loot/LootManager.java
new file mode 100644
index 00000000..eb5451a6
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/LootManager.java
@@ -0,0 +1,180 @@
+package com.songoda.core.lootables.loot;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.stream.JsonReader;
+import com.songoda.core.compatibility.CompatibleMaterial;
+import com.songoda.core.lootables.Lootables;
+import com.songoda.core.lootables.Modify;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+public class LootManager {
+
+ private final Map registeredLootables = new HashMap<>();
+
+ private final Lootables lootables;
+
+ public LootManager(Lootables lootables) {
+ this.lootables = lootables;
+ }
+
+ public Lootable addLootable(Lootable lootable) {
+ return registeredLootables.put(lootable.getKey(), lootable);
+ }
+
+ public void removeLootable(String key) {
+ registeredLootables.remove(key);
+ File file = new File(lootables.getLootablesDir()
+ + "/" + key.toLowerCase() + ".json");
+ file.delete();
+ }
+
+ public List runLoot(Modify modify, boolean burning, boolean isCharged, ItemStack murderWeapon, EntityType looter, Loot loot, int rerollChance, int looting) {
+ List toDrop = new ArrayList<>();
+ if (modify != null)
+ loot = modify.Modify(loot);
+ if (loot == null) return toDrop;
+
+ if (loot.runChance(looting, murderWeapon) || ((Math.random() * 100) - rerollChance < 0 || rerollChance == 100)
+ && loot.runChance(looting, murderWeapon)) {
+
+ if (loot.getOnlyDropFor().size() != 0
+ && loot.getOnlyDropFor().stream().noneMatch(type -> looter != null && type == looter)
+ || !isCharged && loot.isRequireCharged())
+ return toDrop;
+
+ if (loot.getChildLoot().size() > 0) {
+ List childLoot = loot.getChildLoot();
+ Collections.shuffle(childLoot);
+
+ int amt = loot.getChildDropCount();
+ int success = 0;
+
+ top:
+ for (int i = 0; i < 100; i++) {
+ for (Loot value : childLoot) {
+ if (value == null) continue;
+ if (amt == success) break top;
+ List drops = runLoot(modify, burning, isCharged, murderWeapon, looter, value, rerollChance, looting);
+ if (!drops.isEmpty()) success++;
+ toDrop.addAll(drops);
+ }
+ }
+ }
+
+ CompatibleMaterial material = loot.getMaterial();
+ String command = loot.getCommand();
+ int xp = loot.getXp();
+
+ if (material == null && command == null) return toDrop;
+
+ int amount = loot.getAmountToDrop(looting);
+ if (amount == 0) return toDrop;
+
+ if (material != null) {
+ ItemStack item = loot.getBurnedMaterial() != null && burning
+ ? loot.getBurnedMaterial().getItem() : material.getItem();
+ item.setAmount(amount);
+ ItemMeta meta = item.getItemMeta() == null ? Bukkit.getItemFactory().getItemMeta(loot.getMaterial().getMaterial())
+ : item.getItemMeta();
+
+ if (loot.getName() != null)
+ meta.setDisplayName(loot.getName());
+
+ if (loot.getLore() != null)
+ meta.setLore(loot.getLore());
+ item.setItemMeta(meta);
+
+ if (loot.getEnchants(item) != null)
+ item = loot.getEnchants(item);
+
+ if (loot.getDamageMax() != 0 && loot.getDamageMin() != 0) {
+ short max = item.getType().getMaxDurability();
+ short min = (short) (max * (10 / 100.0f));
+ item.setDurability((short) (new Random().nextInt(max - min + 1) + min));
+ }
+
+ toDrop.add(new Drop(item));
+ }
+ if (command != null) {
+ for (int i = 0; i < amount; i++)
+ toDrop.add(new Drop(command));
+ }
+ if (xp != 0) {
+ for (int i = 0; i < amount; i++)
+ toDrop.add(new Drop(xp));
+ }
+ }
+ return toDrop;
+ }
+
+ public void loadLootables() {
+ registeredLootables.clear();
+ File dir = new File(lootables.getLootablesDir());
+ File[] directoryListing = dir.listFiles();
+ if (directoryListing != null) {
+ for (File file : directoryListing) {
+ if (!file.getName().endsWith(".json")) continue;
+ try {
+ Gson gson = new Gson();
+ JsonReader reader = new JsonReader(new FileReader(file.getPath()));
+
+ Lootable lootable = gson.fromJson(reader, Lootable.class);
+
+ if (lootable.getRegisteredLoot().size() != 0)
+ addLootable(lootable);
+
+ reader.close();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public void saveLootables(boolean defaults) {
+ File dir = new File(lootables.getLootablesDir());
+ dir.mkdir();
+
+ // Save to file
+ for (Lootable lootable : registeredLootables.values()) {
+ try {
+
+ File file = new File(lootables.getLootablesDir()
+ + "/" + lootable.getKey().toLowerCase() + ".json");
+ if (file.exists() && defaults) continue;
+
+ try (Writer writer = new FileWriter(file.getPath())) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ gson.toJson(lootable, writer);
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (defaults)
+ registeredLootables.clear();
+ }
+
+ public Map getRegisteredLootables() {
+ return Collections.unmodifiableMap(registeredLootables);
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/Lootable.java b/Core/src/main/java/com/songoda/core/lootables/loot/Lootable.java
new file mode 100644
index 00000000..9640b409
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/Lootable.java
@@ -0,0 +1,43 @@
+package com.songoda.core.lootables.loot;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Lootable {
+
+ // The key applicable to this lootable.
+ @SerializedName("Type")
+ private final String type;
+
+ // Registered loot.
+ @SerializedName("Loot")
+ private final List registeredLoot = new ArrayList<>();
+
+ public Lootable(String key) {
+ this.type = key;
+ }
+
+ public Lootable(String key, Loot... loots) {
+ this.type = key;
+ registeredLoot.addAll(Arrays.asList(loots));
+ }
+
+ public List getRegisteredLoot() {
+ return new ArrayList<>(registeredLoot);
+ }
+
+ public void registerLoot(Loot... loots) {
+ registeredLoot.addAll(Arrays.asList(loots));
+ }
+
+ public String getKey() {
+ return type;
+ }
+
+ public void removeLoot(Loot loot) {
+ this.registeredLoot.remove(loot);
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/lootables/loot/objects/EnchantChance.java b/Core/src/main/java/com/songoda/core/lootables/loot/objects/EnchantChance.java
new file mode 100644
index 00000000..915453ed
--- /dev/null
+++ b/Core/src/main/java/com/songoda/core/lootables/loot/objects/EnchantChance.java
@@ -0,0 +1,28 @@
+package com.songoda.core.lootables.loot.objects;
+
+import org.bukkit.enchantments.Enchantment;
+
+public class EnchantChance {
+
+ private final Enchantment enchantment;
+ private final int level;
+ private final double chanceOverride;
+
+ public EnchantChance(Enchantment enchantment, int level, double chanceOverride) {
+ this.enchantment = enchantment;
+ this.level = level;
+ this.chanceOverride = chanceOverride;
+ }
+
+ public Enchantment getEnchantment() {
+ return enchantment;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public double getChanceOverride() {
+ return chanceOverride;
+ }
+}
diff --git a/Core/src/main/java/com/songoda/core/utils/ItemUtils.java b/Core/src/main/java/com/songoda/core/utils/ItemUtils.java
index e42102f9..abde1616 100644
--- a/Core/src/main/java/com/songoda/core/utils/ItemUtils.java
+++ b/Core/src/main/java/com/songoda/core/utils/ItemUtils.java
@@ -34,6 +34,7 @@ import java.lang.reflect.Method;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
+import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -77,6 +78,43 @@ public class ItemUtils {
return titleCase.toString().trim();
}
+ private static Method methodAsBukkitCopy, methodAsNMSCopy, methodA;
+
+ static {
+ try {
+ Class> clazzEnchantmentManager = ClassMapping.ENCHANTMENT_MANAGER.getClazz();
+ Class> clazzItemStack = ClassMapping.ITEM_STACK.getClazz();
+ Class> clazzCraftItemStack = ClassMapping.CRAFT_ITEM_STACK.getClazz();
+
+ methodAsBukkitCopy = clazzCraftItemStack.getMethod("asBukkitCopy", clazzItemStack);
+ methodAsNMSCopy = clazzCraftItemStack.getMethod("asNMSCopy", ItemStack.class);
+
+ if (ServerVersion.isServerVersion(ServerVersion.V1_8))
+ methodA = clazzEnchantmentManager.getMethod("a", Random.class, clazzItemStack, int.class);
+ else
+ methodA = clazzEnchantmentManager.getMethod("a", Random.class, clazzItemStack, int.class, boolean.class);
+
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static ItemStack applyRandomEnchants(ItemStack item, int level) {
+ try {
+ Object nmsItemStack = methodAsNMSCopy.invoke(null, item);
+
+ if (ServerVersion.isServerVersion(ServerVersion.V1_8))
+ nmsItemStack = methodA.invoke(null, new Random(), nmsItemStack, level);
+ else
+ nmsItemStack = methodA.invoke(null, new Random(), nmsItemStack, level, false);
+
+ item = (ItemStack) methodAsBukkitCopy.invoke(null, nmsItemStack);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ return item;
+ }
+
public static String itemStackArrayToBase64(ItemStack[] items) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
diff --git a/NMS/NMS-API/pom.xml b/NMS/NMS-API/pom.xml
index 83655dfb..9c5ec0bb 100644
--- a/NMS/NMS-API/pom.xml
+++ b/NMS/NMS-API/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_10_R1/pom.xml b/NMS/NMS-v1_10_R1/pom.xml
index 1b3ac3d3..26455a21 100644
--- a/NMS/NMS-v1_10_R1/pom.xml
+++ b/NMS/NMS-v1_10_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_11_R1/pom.xml b/NMS/NMS-v1_11_R1/pom.xml
index 31bedb09..cc675662 100644
--- a/NMS/NMS-v1_11_R1/pom.xml
+++ b/NMS/NMS-v1_11_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_12_R1/pom.xml b/NMS/NMS-v1_12_R1/pom.xml
index 6bbbfdb7..bbb4d824 100644
--- a/NMS/NMS-v1_12_R1/pom.xml
+++ b/NMS/NMS-v1_12_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_13_R1/pom.xml b/NMS/NMS-v1_13_R1/pom.xml
index 14ccb5ee..1d1808f8 100644
--- a/NMS/NMS-v1_13_R1/pom.xml
+++ b/NMS/NMS-v1_13_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_13_R2/pom.xml b/NMS/NMS-v1_13_R2/pom.xml
index 9fb30982..d04c7975 100644
--- a/NMS/NMS-v1_13_R2/pom.xml
+++ b/NMS/NMS-v1_13_R2/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_14_R1/pom.xml b/NMS/NMS-v1_14_R1/pom.xml
index 690b4488..b74f3bc8 100644
--- a/NMS/NMS-v1_14_R1/pom.xml
+++ b/NMS/NMS-v1_14_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_15_R1/pom.xml b/NMS/NMS-v1_15_R1/pom.xml
index a0b7067a..3b988f23 100644
--- a/NMS/NMS-v1_15_R1/pom.xml
+++ b/NMS/NMS-v1_15_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_16_R1/pom.xml b/NMS/NMS-v1_16_R1/pom.xml
index eff6b877..aae8e284 100644
--- a/NMS/NMS-v1_16_R1/pom.xml
+++ b/NMS/NMS-v1_16_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_16_R2/pom.xml b/NMS/NMS-v1_16_R2/pom.xml
index 76b18bd4..903bb8ca 100644
--- a/NMS/NMS-v1_16_R2/pom.xml
+++ b/NMS/NMS-v1_16_R2/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_16_R3/pom.xml b/NMS/NMS-v1_16_R3/pom.xml
index aa92167c..c935bd14 100644
--- a/NMS/NMS-v1_16_R3/pom.xml
+++ b/NMS/NMS-v1_16_R3/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_17_R1/pom.xml b/NMS/NMS-v1_17_R1/pom.xml
index 324c002a..5623f4af 100644
--- a/NMS/NMS-v1_17_R1/pom.xml
+++ b/NMS/NMS-v1_17_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_8_R1/pom.xml b/NMS/NMS-v1_8_R1/pom.xml
index a892224a..94dfeb06 100644
--- a/NMS/NMS-v1_8_R1/pom.xml
+++ b/NMS/NMS-v1_8_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_8_R2/pom.xml b/NMS/NMS-v1_8_R2/pom.xml
index fd7227ea..cc427dc3 100644
--- a/NMS/NMS-v1_8_R2/pom.xml
+++ b/NMS/NMS-v1_8_R2/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_8_R3/pom.xml b/NMS/NMS-v1_8_R3/pom.xml
index 4b35f42f..325d6254 100644
--- a/NMS/NMS-v1_8_R3/pom.xml
+++ b/NMS/NMS-v1_8_R3/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_9_R1/pom.xml b/NMS/NMS-v1_9_R1/pom.xml
index d6275f16..f3219318 100644
--- a/NMS/NMS-v1_9_R1/pom.xml
+++ b/NMS/NMS-v1_9_R1/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/NMS/NMS-v1_9_R2/pom.xml b/NMS/NMS-v1_9_R2/pom.xml
index 60c775ed..bce6f919 100644
--- a/NMS/NMS-v1_9_R2/pom.xml
+++ b/NMS/NMS-v1_9_R2/pom.xml
@@ -3,7 +3,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
../../
diff --git a/pom.xml b/pom.xml
index ef0a0a6f..d9063a27 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
com.songoda
SongodaCore-Modules
- 2.5.4
+ 2.5.5
4.0.0
pom