package net.glowstone.inventory; import net.glowstone.EventFactory; import net.glowstone.GlowServer; import net.glowstone.constants.GlowEnchantment; import net.glowstone.entity.GlowPlayer; import net.glowstone.util.WeightedRandom; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import java.util.*; public class EnchantmentManager { private static final MaterialMatcher[] ENCHANTABLE_TOOLS = new MaterialMatcher[]{ToolType.AXE, ToolType.PICKAXE, ToolType.SPADE}; private final Random random = new Random(); private final GlowPlayer player; private final GlowEnchantingInventory inventory; private int xpSeed; private final int[] enchLevelCosts = new int[3]; private final int[] enchId = new int[3]; private final int[] enchLevel = new int[3]; public EnchantmentManager(GlowEnchantingInventory inventory, GlowPlayer player) { this.player = player; this.inventory = inventory; this.xpSeed = player.getXpSeed(); } //////////////////////////// // Public functions public void invalidate() { ItemStack item = inventory.getItem(); ItemStack resource = inventory.getSecondary(); if (item == null || !canEnchant(item) || player.getGameMode() != GameMode.CREATIVE && (resource == null || resource.getType() != Material.INK_SACK || resource.getDurability() != 4)) { clearEnch(); } else { calculateNewEnchantsAndLevels(); } } public void onPlayerEnchant(int clicked) { if (enchLevelCosts[clicked] <= 0 || isMaliciousClicked(clicked)) return; ItemStack item = inventory.getItem(); List<LeveledEnchant> enchants = calculateCurrentEnchants(item, clicked, enchLevelCosts[clicked]); if (enchants == null) enchants = new ArrayList<>(); EnchantItemEvent event = EventFactory.callEvent(new EnchantItemEvent(player, player.getOpenInventory(), inventory.getLocation().getBlock(), item.clone(), enchLevelCosts[clicked], toMap(enchants), clicked)); if (event.isCancelled() || (player.getGameMode() != GameMode.CREATIVE && event.getExpLevelCost() > player.getLevel())) return; boolean isBook = item.getType() == Material.BOOK; if (isBook) item.setType(Material.ENCHANTED_BOOK); Map<Enchantment, Integer> toAdd = event.getEnchantsToAdd(); if (toAdd == null || toAdd.isEmpty()) { return; } for (Map.Entry<Enchantment, Integer> enchantment : toAdd.entrySet()) { try { if (isBook) { EnchantmentStorageMeta meta = (EnchantmentStorageMeta) item.getItemMeta(); meta.addStoredEnchant(enchantment.getKey(), enchantment.getValue(), true); //TODO is true correct here? item.setItemMeta(meta); } else { item.addUnsafeEnchantment(enchantment.getKey(), enchantment.getValue()); } } catch (IllegalArgumentException e) { //ignore, since plugins are allowed to add enchantments that can't be applied } } player.enchanted(clicked); if (player.getGameMode() != GameMode.CREATIVE) { ItemStack res = inventory.getSecondary(); res.setAmount(res.getAmount() - clicked + 1); if (res.getAmount() <= 0) inventory.setSecondary(null); } this.xpSeed = player.getXpSeed(); update(); } ///////////////////////////// // Enchantments calculating private void calculateNewEnchantsAndLevels() { random.setSeed(xpSeed); int realBookshelfs = inventory.getBookshelfCount(); int countBookshelf = Math.min(15, realBookshelfs); for (int i = 0; i < enchLevelCosts.length; i++) { enchLevelCosts[i] = calculateLevelCost(i, countBookshelf); enchId[i] = -1; enchLevel[i] = -1; } ItemStack item = inventory.getItem(); PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, player.getOpenInventory(), inventory.getLocation().getBlock(), item, enchLevelCosts, realBookshelfs); event.setCancelled(!canEnchant(item)); EventFactory.callEvent(event); if (event.isCancelled()) { for (int i = 0; i < enchLevelCosts.length; i++) enchLevelCosts[i] = 0; } else { for (int i = 0; i < enchLevelCosts.length; i++) { if (enchLevelCosts[i] == 0) continue; List<LeveledEnchant> enchants = calculateCurrentEnchants(item, i, enchLevelCosts[i]); if (enchants != null && !enchants.isEmpty()) { LeveledEnchant chosen = WeightedRandom.getRandom(random, enchants); this.enchId[i] = chosen.getEnchantment().getId(); this.enchLevel[i] = chosen.getLevel(); } } } update(); } private List<LeveledEnchant> calculateCurrentEnchants(ItemStack item, int level, int cost) { random.setSeed(xpSeed + level); int modifier = calculateRandomizedModifier(random, item, cost); if (modifier <= 0) return null; List<LeveledEnchant> possibleEnchants = getAllPossibleEnchants(item, modifier); if (possibleEnchants == null || possibleEnchants.isEmpty()) return null; LeveledEnchant chosen = WeightedRandom.getRandom(random, possibleEnchants); if (chosen == null) return null; List<LeveledEnchant> enchants = new ArrayList<>(); enchants.add(chosen); while (random.nextInt(50) <= modifier) { removeConflicting(enchants, possibleEnchants); if (!possibleEnchants.isEmpty()) { enchants.add(WeightedRandom.getRandom(random, possibleEnchants)); } modifier /= 2; } if (item.getType() == Material.BOOK && enchants.size() > 1) { enchants.remove(random.nextInt(enchants.size())); } return enchants; } private int calculateLevelCost(int stage, int countBookshelf) { int modifier = calculateModifier(inventory.getItem()); if (modifier <= 0) return 0; int rand = random.nextInt(8) + random.nextInt(countBookshelf + 1); rand += 1; rand += countBookshelf / 2; int result; if (stage == 0) { result = Math.max(rand / 3, 1); } else if (stage == 1) { result = rand * 2 / 3 + 1; } else { result = Math.max(rand, countBookshelf * 2); } if (result < stage + 1) return 0; else return result; } ////////////////////////////////// // Modifier calculation private static int calculateRandomizedModifier(Random random, ItemStack itemStack, int cost) { int modifier = calculateModifier(itemStack); if (modifier <= 0) return -1; float randomValue = 1 + (random.nextFloat() + random.nextFloat() - 1.0F) * 0.15F; modifier /= 4; modifier += 1; modifier = random.nextInt(modifier) + random.nextInt(modifier); modifier += 1 + cost; modifier = Math.round(modifier * randomValue); modifier = Math.max(1, modifier); return modifier; } private static int calculateModifier(ItemStack item) { //TODO: replace this by a better system? Material type = item.getType(); switch (type) { case BOOK: case BOW: case FISHING_ROD: return 1; } if (ClothType.CHAINMAIL.matches(type)) return 12; else if (ClothType.IRON.matches(type)) return 9; else if (ClothType.DIAMOND.matches(type)) return 10; else if (ClothType.LEATHER.matches(type)) return 15; else if (ClothType.GOLD.matches(type)) return 25; else if (MaterialToolType.WOOD.matches(type)) return 15; else if (MaterialToolType.STONE.matches(type)) return 5; else if (MaterialToolType.DIAMOND.matches(type)) return 10; else if (MaterialToolType.IRON.matches(type)) return 14; else if (MaterialToolType.GOLD.matches(type)) return 22; return 0; } ///////////////////////////////////// // Internal stuff / helper functions private void clearEnch() { for (int i = 0; i < 3; i++) { enchLevelCosts[i] = 0; enchId[i] = -1; enchLevel[i] = -1; } update(); } private void update() { player.setWindowProperty(InventoryView.Property.ENCHANT_BUTTON1, enchLevelCosts[0]); player.setWindowProperty(InventoryView.Property.ENCHANT_BUTTON2, enchLevelCosts[1]); player.setWindowProperty(InventoryView.Property.ENCHANT_BUTTON3, enchLevelCosts[2]); player.setWindowProperty(InventoryView.Property.ENCHANT_XP_SEED, xpSeed & -16); player.setWindowProperty(InventoryView.Property.ENCHANT_ID_AND_LEVEL1, enchId[0] | enchLevel[0] << 8); player.setWindowProperty(InventoryView.Property.ENCHANT_ID_AND_LEVEL2, enchId[1] | enchLevel[1] << 8); player.setWindowProperty(InventoryView.Property.ENCHANT_ID_AND_LEVEL3, enchId[2] | enchLevel[2] << 8); } private boolean isMaliciousClicked(int clicked) { //TODO: better handling of for malicious clients? if (clicked < 0 || clicked > enchLevelCosts.length) { GlowServer.logger.info("Malicious client, cannot enchant slot " + clicked); update(); return true; } int level = enchLevelCosts[clicked]; if (player.getGameMode() != GameMode.CREATIVE) { if (player.getLevel() < level || inventory.getSecondary() == null || inventory.getSecondary().getAmount() < clicked) { GlowServer.logger.info("Malicious client, player has not enough levels / enough resources to enchant item!"); update(); return true; } } return false; } private static boolean canEnchant(ItemStack item) { Material type = item.getType(); switch (type) { case ENCHANTED_BOOK: return false; case BOOK: return item.getAmount() == 1; case FISHING_ROD: case BOW: return item.getEnchantments().isEmpty(); default: return (isEnchantableTool(type) || isCloth(type) || ToolType.SWORD.matches(type)) && item.getEnchantments().isEmpty(); } } private static boolean isCloth(Material type) { for (MaterialMatcher mm : ClothType.values()) if (mm.matches(type)) return true; return false; } private static boolean isEnchantableTool(Material type) { for (MaterialMatcher mm : ENCHANTABLE_TOOLS) if (mm.matches(type)) return true; return false; } private static Map<Enchantment, Integer> toMap(List<LeveledEnchant> list) { Map<Enchantment, Integer> map = new HashMap<>(list.size()); for (LeveledEnchant enchant : list) map.put(enchant.getEnchantment(), enchant.getLevel()); return map; } private static List<LeveledEnchant> getAllPossibleEnchants(ItemStack item, int modifier) { List<LeveledEnchant> enchantments = new ArrayList<>(); boolean isBook = item.getType() == Material.BOOK; for (Enchantment enchantment : Enchantment.values()) { if (isBook || enchantment.canEnchantItem(item)) { for (int level = enchantment.getStartLevel(); level <= enchantment.getMaxLevel(); level++) { if (((GlowEnchantment) enchantment).isInRange(level, modifier)) { enchantments.add(new LeveledEnchant(enchantment, level)); } } } } return enchantments; } private static void removeConflicting(List<LeveledEnchant> enchants, List<LeveledEnchant> toReduce) { Iterator<LeveledEnchant> it = toReduce.iterator(); while (it.hasNext()) { Enchantment currentEnchantment = it.next().getEnchantment(); boolean conflicts = false; for (LeveledEnchant entry : enchants) { if (entry.getEnchantment().conflictsWith(currentEnchantment)) { conflicts = true; break; } } if (conflicts) it.remove(); } } }