package mineplex.game.clans.items; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import mineplex.core.MiniPlugin; import mineplex.core.account.CoreClientManager; import mineplex.core.common.util.C; import mineplex.core.common.util.F; import mineplex.core.common.util.UtilPlayer; import mineplex.core.common.util.UtilServer; import mineplex.core.common.weight.Weight; import mineplex.core.common.weight.WeightSet; import mineplex.core.donation.DonationManager; import mineplex.core.packethandler.IPacketHandler; import mineplex.core.packethandler.PacketHandler; import mineplex.core.packethandler.PacketInfo; import mineplex.game.clans.items.attributes.AttributeContainer; import mineplex.game.clans.items.attributes.AttributeType; import mineplex.game.clans.items.attributes.ItemAttribute; import mineplex.game.clans.items.attributes.armor.ConqueringArmorAttribute; import mineplex.game.clans.items.attributes.armor.LavaAttribute; import mineplex.game.clans.items.attributes.armor.PaddedAttribute; import mineplex.game.clans.items.attributes.armor.ReinforcedAttribute; import mineplex.game.clans.items.attributes.armor.SlantedAttribute; import mineplex.game.clans.items.attributes.bow.HeavyArrowsAttribute; import mineplex.game.clans.items.attributes.bow.HuntingAttribute; import mineplex.game.clans.items.attributes.bow.InverseAttribute; import mineplex.game.clans.items.attributes.bow.LeechingAttribute; import mineplex.game.clans.items.attributes.bow.RecursiveAttribute; import mineplex.game.clans.items.attributes.bow.ScorchingAttribute; import mineplex.game.clans.items.attributes.bow.SlayingAttribute; import mineplex.game.clans.items.attributes.weapon.ConqueringAttribute; import mineplex.game.clans.items.attributes.weapon.FlamingAttribute; import mineplex.game.clans.items.attributes.weapon.FrostedAttribute; import mineplex.game.clans.items.attributes.weapon.HasteAttribute; import mineplex.game.clans.items.attributes.weapon.JaggedAttribute; import mineplex.game.clans.items.attributes.weapon.SharpAttribute; import mineplex.game.clans.items.commands.GearCommand; import mineplex.game.clans.items.economy.GoldToken; import mineplex.game.clans.items.legendaries.AlligatorsTooth; import mineplex.game.clans.items.legendaries.ChitauriScepter; import mineplex.game.clans.items.legendaries.EnergyCrossbow; import mineplex.game.clans.items.legendaries.GiantsBroadsword; import mineplex.game.clans.items.legendaries.HyperAxe; import mineplex.game.clans.items.legendaries.LegendaryItem; import mineplex.game.clans.items.legendaries.MagneticMaul; import mineplex.game.clans.items.legendaries.WindBlade; import mineplex.game.clans.items.smelting.SmeltingListener; import mineplex.game.clans.items.ui.GearShop; import mineplex.serverdata.serialization.RuntimeTypeAdapterFactory; import net.minecraft.server.v1_8_R3.NBTTagCompound; import net.minecraft.server.v1_8_R3.Packet; import net.minecraft.server.v1_8_R3.PacketPlayOutSetSlot; import net.minecraft.server.v1_8_R3.PacketPlayOutWindowItems; /** * Manages creation and retrieval of associated {@link PlayerGear}s with online * players, as well as offering methods for parsing and handling * {@link CustomItem}s. * * @author MrTwiggy * */ public class GearManager extends MiniPlugin implements IPacketHandler, Runnable { private static final String ITEM_SERIALIZATION_TAG = "-JSON-"; private static Gson _gson; private static GearManager _instance; // Singleton instance private Map _playerGears; // Mapping of player names // (key) to cached gear set // (value). private WeightSet _attributeWeights; // Weightings for randomly // selecting number of // attributes (1, 2, 3) private WeightSet _typeWeights; // Weightings for randomly // selecting item type // (legendary/weapon/armor/bow) private Set _creativePlayers; // Set of names for all players // currently in Creative gamemode // Legendary generation private WeightSet> _legendaryWeights; // Weapon generation private WeightSet _weaponTypes; // Armor generation private WeightSet _armorTypes; // Attribute generation private WeightSet> _weaponAttributes; private WeightSet> _armorAttributes; private WeightSet> _bowAttributes; // Attribute Masks private EnumSet _maskAttributes; private GearShop _shop; public GearManager(JavaPlugin plugin, PacketHandler packetHandler, CoreClientManager clientManager, DonationManager donationManager) { super("CustomGear", plugin); _instance = this; _shop = new GearShop(this, clientManager, donationManager); _creativePlayers = new HashSet(); _playerGears = new HashMap(); // TODO: Introduce configurable non-hardcoded values for generation // weights? _attributeWeights = new WeightSet(new Weight(3, 3), new Weight(20, 2), new Weight(77, 1)); _typeWeights = new WeightSet(new Weight(6, ItemType.LEGENDARY), new Weight(46, ItemType.ARMOR), new Weight(25, ItemType.WEAPON), new Weight(23, ItemType.BOW)); // Weapon-based attributes _weaponAttributes = new WeightSet>(FrostedAttribute.class, SharpAttribute.class, JaggedAttribute.class, HasteAttribute.class, FlamingAttribute.class, ConqueringAttribute.class); // Armor-based attributes _armorAttributes = new WeightSet>(SlantedAttribute.class, ReinforcedAttribute.class, ConqueringArmorAttribute.class, PaddedAttribute.class, LavaAttribute.class); // Bow-based attributes _bowAttributes = new WeightSet>(HeavyArrowsAttribute.class, HuntingAttribute.class, InverseAttribute.class, LeechingAttribute.class, RecursiveAttribute.class, ScorchingAttribute.class, SlayingAttribute.class); // Weapon material types _weaponTypes = new WeightSet(Material.DIAMOND_SWORD, Material.GOLD_SWORD, Material.IRON_SWORD, Material.DIAMOND_AXE, Material.GOLD_AXE, Material.IRON_AXE); // Armor material types _armorTypes = new WeightSet(Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS, Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS, Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS); // TODO: Initialize list of attributes and types // Initialize various LegendaryItem types _legendaryWeights = new WeightSet>(ChitauriScepter.class, EnergyCrossbow.class, AlligatorsTooth.class, WindBlade.class, GiantsBroadsword.class, HyperAxe.class, MagneticMaul.class); // TODO: Add rest of legendaries, find better way? // Register listeners UtilServer.getServer().getPluginManager().registerEvents(new ItemListener(getPlugin()), getPlugin()); UtilServer.getServer().getPluginManager().registerEvents(new SmeltingListener(), getPlugin()); // Initialize attribute types factory for JSON handling of polymorphism. RuntimeTypeAdapterFactory attributeFactory = RuntimeTypeAdapterFactory.of(ItemAttribute.class); for (Class attributeType : _armorAttributes.elements()) { attributeFactory.registerSubtype(attributeType); } for (Class attributeType : _weaponAttributes.elements()) { attributeFactory.registerSubtype(attributeType); } for (Class attributeType : _bowAttributes.elements()) { attributeFactory.registerSubtype(attributeType); } // Initialize legendary item type factory for JSON handling of // polymorphism. RuntimeTypeAdapterFactory customItemType = RuntimeTypeAdapterFactory.of(CustomItem.class); customItemType.registerSubtype(CustomItem.class); customItemType.registerSubtype(LegendaryItem.class); customItemType.registerSubtype(GoldToken.class); for (Class itemType : _legendaryWeights.elements()) { customItemType.registerSubtype(itemType); } // Build GSON instance off factories for future serialization of items. _gson = new GsonBuilder().registerTypeAdapterFactory(attributeFactory).registerTypeAdapterFactory(customItemType).create(); packetHandler.addPacketHandler(this, PacketPlayOutSetSlot.class, PacketPlayOutWindowItems.class); plugin.getServer().getScheduler().runTaskTimer(plugin, this, 1l, 1l); _maskAttributes = EnumSet.of(Material.GOLD_RECORD, Material.GREEN_RECORD, Material.RECORD_3, Material.RECORD_4, Material.RECORD_5, Material.RECORD_6, Material.RECORD_7, Material.RECORD_8, Material.RECORD_9, Material.RECORD_10, Material.RECORD_11, Material.RECORD_12); } @Override public void addCommands() { addCommand(new GearCommand(this)); } public void addCreativePlayer(Player player) { _creativePlayers.add(player.getName()); player.updateInventory(); } public void removeCreativePlayer(Player player) { _creativePlayers.remove(player.getName()); player.updateInventory(); } /** * Tick & update internal logic for {@link GearManager}. Called once per * tick. */ @Override public void run() { Iterator iterator = _playerGears.values().iterator(); while (iterator.hasNext()) { PlayerGear gear = iterator.next(); if (gear.isOnline()) { gear.update(); } else { iterator.remove(); } } } /** * @param player - the player whose {@link PlayerGear} set is to be fetched. * @return the cached or newly instantiated {@link PlayerGear} associated * with {@code player}. */ public PlayerGear getPlayerGear(Player player) { String playerName = player.getName(); if (!_playerGears.containsKey(playerName)) { PlayerGear gear = new PlayerGear(playerName); _playerGears.put(playerName, gear); } return _playerGears.get(playerName); } public CustomItem generateItem() { int attributeCount = _attributeWeights.generateRandom(); ItemType itemType = _typeWeights.generateRandom(); RareItemFactory factory = RareItemFactory.begin(itemType); if (itemType == ItemType.LEGENDARY) { factory.setLegendary(_legendaryWeights.generateRandom()); } else if (itemType == ItemType.ARMOR) { factory.setType(_armorTypes.generateRandom()); } else if (itemType == ItemType.WEAPON) { factory.setType(_weaponTypes.generateRandom()); } else if (itemType == ItemType.BOW) { factory.setType(Material.BOW); } if (itemType != ItemType.LEGENDARY) // Only non-legendaries have // attributes { AttributeContainer attributes = new AttributeContainer(); generateAttributes(attributes, itemType, attributeCount); System.out.println("Generating attributes..."); System.out.println("Remaining size: " + attributes.getRemainingTypes().size()); if (attributes.getSuperPrefix() != null) { System.out.println("Set super prefix: " + attributes.getSuperPrefix().getClass()); factory.setSuperPrefix(attributes.getSuperPrefix().getClass()); } if (attributes.getPrefix() != null) { System.out.println("Set prefix: " + attributes.getPrefix().getClass()); factory.setPrefix(attributes.getPrefix().getClass()); } if (attributes.getSuffix() != null) { System.out.println("Set suffix: " + attributes.getSuffix().getClass()); factory.setSuffix(attributes.getSuffix().getClass()); } } return factory.getWrapper(); } private void generateAttributes(AttributeContainer container, ItemType type, int count) { for (int i = 0; i < count; i++) { int attempts = 0; Set remaining = container.getRemainingTypes(); ItemAttribute attribute = null; while (remaining.size() > 0 && attempts < 10 && attribute == null) { ItemAttribute sampleAttribute = null; switch (type) { case ARMOR: sampleAttribute = instantiate(_armorAttributes.generateRandom()); break; case WEAPON: sampleAttribute = instantiate(_weaponAttributes.generateRandom()); break; case BOW: sampleAttribute = instantiate(_bowAttributes.generateRandom()); break; default: break; } if (sampleAttribute != null && remaining.contains(sampleAttribute.getType())) { attribute = sampleAttribute; // Select valid attribute to // add } } container.addAttribute(attribute); } } public void spawnItem(Location location) { CustomItem item = generateItem(); location.getWorld().dropItem(location, item.toItemStack()); } public static CustomItem parseItem(ItemStack item) { String serialization = getItemSerialization(item); if (serialization != null) { CustomItem customItem = null; try { customItem = deserialize(serialization); } catch (Exception exception) { exception.printStackTrace(); System.out.println("=========="); System.out.println("GearManager parse problem :"); System.out.println(serialization); System.out.println("=========="); } return customItem; } return null; // No serialization found in item's lore, not a custom // item! } public static boolean isCustomItem(ItemStack item) { return getItemSerialization(item) != null; } public static String getItemSerialization(CustomItem customItem) { String serialization = serialize(customItem); return ITEM_SERIALIZATION_TAG + serialization; } /** * @param type - the class-type of the object to be instantiated. (must have * zero-argument constructor) * @return a newly instantiated instance of {@code type} class-type. * Instantied with zero argument constructor. */ private static T instantiate(Class type) { try { return type.newInstance(); } catch (Exception e) { return null; } } private static String getItemSerialization(ItemStack item) { if (item == null || item.getItemMeta() == null || item.getItemMeta().getLore() == null) return null; ItemMeta meta = item.getItemMeta(); for (String lore : meta.getLore()) { if (lore.startsWith(ITEM_SERIALIZATION_TAG)) // Found serialization // lore-line { int tagLength = ITEM_SERIALIZATION_TAG.length(); String serialization = lore.substring(tagLength); return serialization; } } return null; // Unable to find any serialized lore lines, hence not a // CustomItem. } public static String serialize(CustomItem customItem) { return _gson.toJson(customItem, CustomItem.class); } public static String serialize(AttributeContainer attributes) { return _gson.toJson(attributes, AttributeContainer.class); } public static CustomItem deserialize(String serialization) { return _gson.fromJson(serialization, CustomItem.class); } public static T deserialize(String serialization, Class type) { return _gson.fromJson(serialization, type); } /** * @return singleton instance of {@link GearManager}. */ public static GearManager getInstance() { return _instance; } public static void notify(Player player, String message) { UtilPlayer.message(player, F.main("Gear", message)); } /** * @param player - the player to see if they should have their out-going * packets masked on CustomGear items. * @return true, if the player should have their gear lore masked, false * otherwise. */ private boolean maskGearPacket(Player player) { return player.getGameMode() != GameMode.CREATIVE && !_creativePlayers.contains(player.getName()); } public void handle(PacketInfo packetInfo) { // Don't mask custom gear lore for creative players, as this will break // them. if (!maskGearPacket(packetInfo.getPlayer())) return; Packet packet = packetInfo.getPacket(); if (packet instanceof PacketPlayOutSetSlot) { PacketPlayOutSetSlot slotPacket = (PacketPlayOutSetSlot) packet; slotPacket.c = maskItem(slotPacket.c, packetInfo.getPlayer()); // Mask all out-going item // packets } else if (packet instanceof PacketPlayOutWindowItems) { PacketPlayOutWindowItems itemsPacket = (PacketPlayOutWindowItems) packet; for (int i = 0; i < itemsPacket.b.length; i++) { itemsPacket.b[i] = maskItem(itemsPacket.b[i], packetInfo.getPlayer()); // Mask all // out-going // item packets ItemStack item = CraftItemStack.asCraftMirror(itemsPacket.b[i]); if (item != null && _maskAttributes.contains(item.getType())) itemsPacket.b[i] = removeAttributes(itemsPacket.b[i]); } } } private net.minecraft.server.v1_8_R3.ItemStack removeAttributes(net.minecraft.server.v1_8_R3.ItemStack item) { if (item == null) return null; if (item.getTag() == null) { item.setTag(new NBTTagCompound()); } item.getTag().setInt("HideFlags", 62); return item; } private net.minecraft.server.v1_8_R3.ItemStack maskItem(net.minecraft.server.v1_8_R3.ItemStack item, Player player) { // Cannot mask a null item if (item == null) { return null; } CraftItemStack originalItem = CraftItemStack.asCraftMirror(item); ItemMeta originalMeta = originalItem.getItemMeta(); // No need to modify item packets with no lore if (originalMeta == null || originalMeta.getLore() == null) { return item; } List lore = new ArrayList(); CustomItem ci = parseItem(originalItem); for (String line : originalMeta.getLore()) { if (!line.startsWith(ITEM_SERIALIZATION_TAG)) { lore.add(line); } } if (ci != null && _legendaryWeights.elements().contains(ci.getClass())) { lore.add(C.cWhite + "Original Owner: " + C.cYellow + (ci.OriginalOwner == null ? "You" : Bukkit.getOfflinePlayer(UUID.fromString(ci.OriginalOwner)).getName())); } net.minecraft.server.v1_8_R3.ItemStack newItem = CraftItemStack.asNMSCopy(originalItem); CraftItemStack newCopy = CraftItemStack.asCraftMirror(newItem); ItemMeta newMeta = newCopy.getItemMeta(); newMeta.setLore(lore); newCopy.setItemMeta(newMeta); return newItem; } public void openShop(Player player) { _shop.attemptShopOpen(player); } }