diff --git a/src/main/java/de/epiceric/shopchest/config/Config.java b/src/main/java/de/epiceric/shopchest/config/Config.java index 486df2c..391a1f1 100644 --- a/src/main/java/de/epiceric/shopchest/config/Config.java +++ b/src/main/java/de/epiceric/shopchest/config/Config.java @@ -68,6 +68,9 @@ public class Config { /** Whether shops should be protected by explosions **/ public boolean explosion_protection; + /** Whether hologram interaction should be enabled **/ + public boolean enable_hologram_interaction; + /** Whether the debug log file should be created **/ public boolean enable_debug_log; @@ -277,6 +280,7 @@ public class Config { buy_greater_or_equal_sell = plugin.getConfig().getBoolean("buy-greater-or-equal-sell"); hopper_protection = plugin.getConfig().getBoolean("hopper-protection"); two_line_prices = plugin.getConfig().getBoolean("two-line-prices"); + enable_hologram_interaction = plugin.getConfig().getBoolean("enable-hologram-interaction"); enable_debug_log = plugin.getConfig().getBoolean("enable-debug-log"); explosion_protection = plugin.getConfig().getBoolean("explosion-protection"); exclude_admin_shops = plugin.getConfig().getBoolean("shop-limits.exclude-admin-shops"); diff --git a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java index 5e7575f..fa48e4b 100644 --- a/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java +++ b/src/main/java/de/epiceric/shopchest/listeners/ShopInteractListener.java @@ -13,6 +13,7 @@ import de.epiceric.shopchest.event.ShopInfoEvent; import de.epiceric.shopchest.event.ShopRemoveEvent; import de.epiceric.shopchest.language.LanguageUtils; import de.epiceric.shopchest.language.LocalizedMessage; +import de.epiceric.shopchest.nms.Hologram; import de.epiceric.shopchest.shop.Shop; import de.epiceric.shopchest.shop.Shop.ShopType; import de.epiceric.shopchest.sql.Database; @@ -30,12 +31,17 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Chest; import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -122,8 +128,7 @@ public class ShopInteractListener implements Listener { } } - @EventHandler - public void onPlayerInteract(PlayerInteractEvent e) { + private void handleInteractEvent(PlayerInteractEvent e, boolean calledFromInteractEvent) { Block b = e.getClickedBlock(); Player p = e.getPlayer(); @@ -176,24 +181,33 @@ public class ShopInteractListener implements Listener { } else { if (shopUtils.isShop(b.getLocation())) { - e.setCancelled(true); Shop shop = shopUtils.getShop(b.getLocation()); if (p.isSneaking()) { - if (!shop.getVendor().getUniqueId().equals(p.getUniqueId())) { - if (perm.has(p, "shopchest.openOther")) { - String vendorName = (shop.getVendor().getName() == null ? shop.getVendor().getUniqueId().toString() : shop.getVendor().getName()); - p.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.OPENED_SHOP, new LocalizedMessage.ReplacedRegex(Regex.VENDOR, vendorName))); - plugin.debug(p.getName() + " is opening " + vendorName + "'s shop (#" + shop.getID() + ")" ); - e.setCancelled(false); + if (Utils.getPreferredItemInHand(p) == null) { + e.setCancelled(true); + if (!shop.getVendor().getUniqueId().equals(p.getUniqueId())) { + if (perm.has(p, "shopchest.openOther")) { + String vendorName = (shop.getVendor().getName() == null ? shop.getVendor().getUniqueId().toString() : shop.getVendor().getName()); + p.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.OPENED_SHOP, new LocalizedMessage.ReplacedRegex(Regex.VENDOR, vendorName))); + plugin.debug(p.getName() + " is opening " + vendorName + "'s shop (#" + shop.getID() + ")"); + e.setCancelled(false); + if (!calledFromInteractEvent) { + p.openInventory(shop.getInventoryHolder().getInventory()); + } + } else { + p.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.NO_PERMISSION_OPEN_OTHERS)); + plugin.debug(p.getName() + " is not permitted to open another player's shop"); + } } else { - p.sendMessage(LanguageUtils.getMessage(LocalizedMessage.Message.NO_PERMISSION_OPEN_OTHERS)); - plugin.debug(p.getName() + " is not permitted to open another player's shop"); + e.setCancelled(false); + if (!calledFromInteractEvent) { + p.openInventory(shop.getInventoryHolder().getInventory()); + } } - } else { - e.setCancelled(false); } } else { + e.setCancelled(true); if (shop.getShopType() == ShopType.ADMIN || !shop.getVendor().getUniqueId().equals(p.getUniqueId())) { plugin.debug(p.getName() + " wants to buy"); if (shop.getBuyPrice() > 0) { @@ -245,10 +259,12 @@ public class ShopInteractListener implements Listener { } } else { e.setCancelled(false); + if (!calledFromInteractEvent) { + p.openInventory(shop.getInventoryHolder().getInventory()); + } } } } - } @@ -309,7 +325,75 @@ public class ShopInteractListener implements Listener { } else { ClickType.removePlayerClickType(p); } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent e) { + handleInteractEvent(e, true); + } + + @EventHandler + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent e) { + if (!plugin.getShopChestConfig().enable_hologram_interaction) return; + + Entity entity = e.getRightClicked(); + Player p = e.getPlayer(); + + if (e.getHand() == EquipmentSlot.HAND) { + if (entity instanceof ArmorStand) { + ArmorStand armorStand = (ArmorStand) entity; + if (Hologram.isPartOfHologram(armorStand)) { + Hologram hologram = Hologram.getHologram(armorStand); + if (hologram != null) { + Block b = null; + for (Shop shop : plugin.getShopUtils().getShops()) { + if (shop.getHologram().equals(hologram)) { + b = shop.getLocation().getBlock(); + } + } + + if (b != null) { + PlayerInteractEvent interactEvent = new PlayerInteractEvent(p, Action.RIGHT_CLICK_BLOCK, Utils.getPreferredItemInHand(p), b, null, EquipmentSlot.HAND); + handleInteractEvent(interactEvent, false); + } + + } + } + } + } + } + + @EventHandler + public void onPlayerDamageEntity(EntityDamageByEntityEvent e) { + if (!plugin.getShopChestConfig().enable_hologram_interaction) return; + + Entity entity = e.getEntity(); + Entity damager = e.getDamager(); + + if (!(damager instanceof Player)) return; + Player p = (Player) damager; + + if (entity instanceof ArmorStand) { + ArmorStand armorStand = (ArmorStand) entity; + if (Hologram.isPartOfHologram(armorStand)) { + Hologram hologram = Hologram.getHologram(armorStand); + if (hologram != null) { + Block b = null; + for (Shop shop : plugin.getShopUtils().getShops()) { + if (shop.getHologram().equals(hologram)) { + b = shop.getLocation().getBlock(); + } + } + + if (b != null) { + PlayerInteractEvent interactEvent = new PlayerInteractEvent(p, Action.LEFT_CLICK_BLOCK, Utils.getPreferredItemInHand(p), b, null, EquipmentSlot.HAND); + handleInteractEvent(interactEvent, false); + } + + } + } + } } /** diff --git a/src/main/java/de/epiceric/shopchest/nms/Hologram.java b/src/main/java/de/epiceric/shopchest/nms/Hologram.java index 131ea33..9fd5f46 100644 --- a/src/main/java/de/epiceric/shopchest/nms/Hologram.java +++ b/src/main/java/de/epiceric/shopchest/nms/Hologram.java @@ -3,19 +3,23 @@ package de.epiceric.shopchest.nms; import de.epiceric.shopchest.ShopChest; import de.epiceric.shopchest.utils.Utils; import org.bukkit.Location; -import org.bukkit.entity.Entity; +import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Player; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.UUID; public class Hologram { + private static List holograms = new ArrayList<>(); + private boolean exists = false; - private int count; private List entityList = new ArrayList<>(); + private List entityUuidList = new ArrayList<>(); private String[] text; private Location location; private List visible = new ArrayList<>(); @@ -25,6 +29,7 @@ public class Hologram { private Class nmsWorldClass = Utils.getNMSClass("World"); private Class packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving"); private Class packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy"); + private Class entityClass = Utils.getNMSClass("Entity"); private Class entityLivingClass = Utils.getNMSClass("EntityLiving"); public Hologram(ShopChest plugin, String[] text, Location location) { @@ -33,8 +38,8 @@ public class Hologram { this.location = location; Class[] requiredClasses = new Class[] { - nmsWorldClass, entityArmorStandClass, entityLivingClass, - packetPlayOutSpawnEntityLivingClass, packetPlayOutEntityDestroyClass, + nmsWorldClass, entityArmorStandClass, entityLivingClass, entityClass, + packetPlayOutSpawnEntityLivingClass, packetPlayOutEntityDestroyClass }; for (Class c : requiredClasses) { @@ -48,19 +53,35 @@ public class Hologram { } private void create() { - for (String text : this.text) { - if (text == null) - continue; + Location loc = location.clone(); + + for (int i = 0; i <= text.length; i++) { + String text = null; + + if (i != this.text.length) { + text = this.text[i]; + if (text == null) continue; + } else { + if (!plugin.getShopChestConfig().enable_hologram_interaction) { + loc = location.clone(); + loc.add(0, 1, 0); + } else { + continue; + } + } try { - Object craftWorld = this.location.getWorld().getClass().cast(this.location.getWorld()); - Object nmsWorld = nmsWorldClass.cast(craftWorld.getClass().getMethod("getHandle").invoke(craftWorld)); + Object craftWorld = loc.getWorld().getClass().cast(loc.getWorld()); + Object nmsWorldServer = craftWorld.getClass().getMethod("getHandle").invoke(craftWorld); Constructor entityArmorStandConstructor = entityArmorStandClass.getConstructor(nmsWorldClass, double.class, double.class, double.class); - Object entityArmorStand = entityArmorStandConstructor.newInstance(nmsWorld, this.location.getX(), this.location.getY(), this.getLocation().getZ()); + Object entityArmorStand = entityArmorStandConstructor.newInstance(nmsWorldServer, loc.getX(), loc.getY(),loc.getZ()); + + if (text != null) { + entityArmorStandClass.getMethod("setCustomName", String.class).invoke(entityArmorStand, text); + entityArmorStandClass.getMethod("setCustomNameVisible", boolean.class).invoke(entityArmorStand, true); + } - entityArmorStandClass.getMethod("setCustomName", String.class).invoke(entityArmorStand, text); - entityArmorStandClass.getMethod("setCustomNameVisible", boolean.class).invoke(entityArmorStand, true); entityArmorStandClass.getMethod("setInvisible", boolean.class).invoke(entityArmorStand, true); if (Utils.getMajorVersion() < 10) { @@ -69,9 +90,17 @@ public class Hologram { entityArmorStandClass.getMethod("setNoGravity", boolean.class).invoke(entityArmorStand, true); } + // Probably like an addEntity() method... + Method b = nmsWorldServer.getClass().getDeclaredMethod("b", entityClass); + b.setAccessible(true); + b.invoke(nmsWorldServer, entityArmorStand); + + Object uuid = entityClass.getMethod("getUniqueID").invoke(entityArmorStand); + + entityUuidList.add((UUID) uuid); entityList.add(entityArmorStand); - this.location.subtract(0, 0.25, 0); - count++; + + loc.subtract(0, 0.25, 0); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { plugin.getLogger().severe("Could not create Hologram with reflection"); plugin.debug("Could not create Hologram with reflection"); @@ -81,11 +110,7 @@ public class Hologram { } - for (int i = 0; i < count; i++) { - this.location.add(0, 0.25, 0); - } - - count = 0; + holograms.add(this); exists = true; } @@ -150,6 +175,14 @@ public class Hologram { return exists; } + /** + * @param armorStand Armor stand to check + * @return Whether the given armor stand is part of the hologram + */ + public boolean contains(ArmorStand armorStand) { + return entityUuidList.contains(armorStand.getUniqueId()); + } + /** * Removes the hologram.
* IHologram will be hidden from all players and will be killed @@ -165,6 +198,32 @@ public class Hologram { } } exists = false; + holograms.remove(this); + } + + /** + * @param armorStand Armor stand that's part of a hologram + * @return Hologram, the armor stand is part of + */ + public static Hologram getHologram(ArmorStand armorStand) { + for (Hologram hologram : holograms) { + if (hologram.contains(armorStand)) return hologram; + } + + return null; + } + + /** + * @param armorStand Armor stand to check + * @return Whether the armor stand is part of a hologram + */ + public static boolean isPartOfHologram(ArmorStand armorStand) { + for (Hologram hologram : holograms) { + if (hologram.contains(armorStand)) { + return true; + } + } + return false; } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 13fe84a..e24dbc1 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -14,6 +14,12 @@ language-file: "en_US" # Set whether the floating shop items on top of the chest should be shown show-shop-items: true +# Set whether interaction with the hologram should be enabled. +# If set to true, a player can do the exact same thing with the +# hologram, as with the chest. You can even open the chest if you +# are the vendor or have permission. +enable-hologram-interaction: true + # Set whether a debug log file should be created. # The file may get large! Please enable this setting when reporting bugs. enable-debug-log: false