Client-side shop items

All shop items are now spawned with packets and reflection client-side, so probably duplicated items are history (finally). This also allowed me to remove the ClearLag and LWC dependency, as ClearLag can't remove client-side items and LWC's Magnet Sucker can't suck them inside a chest. I also changed a bit in the classes of the nms package, so all required classes have to be found before attempting to do anything.

Fixes #11 and fixes #4
This commit is contained in:
Eric 2016-08-08 21:55:32 +02:00
parent bb54c7da67
commit a95106a335
15 changed files with 372 additions and 372 deletions

17
pom.xml
View File

@ -24,10 +24,6 @@
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>shopchest-repo</id>
<url>https://epicericee.github.io/ShopChest/maven/</url>
</repository>
<repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases/</url>
@ -41,25 +37,12 @@
<version>1.10.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.milkbowl.vault</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.minebuilders</groupId>
<artifactId>clearlag</artifactId>
<version>2.9.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.griefcraft.lwc</groupId>
<artifactId>lwc-entity-locking</artifactId>
<version>1.7.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
<distributionManagement>

View File

@ -22,9 +22,6 @@ import de.epiceric.shopchest.utils.Utils;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
@ -41,7 +38,6 @@ public class ShopChest extends JavaPlugin {
private Config config = null;
private Economy econ = null;
private Permission perm = null;
private boolean lwc = false;
private Database database;
private boolean isUpdateNeeded = false;
private String latestVersion = "";
@ -238,8 +234,6 @@ public class ShopChest extends JavaPlugin {
}, config.auto_reload_time * 20, config.auto_reload_time * 20);
}
lwc = getServer().getPluginManager().isPluginEnabled("LWC");
Bukkit.getScheduler().runTaskAsynchronously(this, new Runnable() {
@Override
public void run() {
@ -254,7 +248,7 @@ public class ShopChest extends JavaPlugin {
Bukkit.getConsoleSender().sendMessage("[ShopChest] " + LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, latestVersion)));
for (Player p : getServer().getOnlinePlayers()) {
if (p.isOp() || perm.has(p, "shopchest.notification.update")) {
if (perm.has(p, "shopchest.notification.update")) {
JsonBuilder jb = new JsonBuilder(ShopChest.this, LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, latestVersion)), LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_CLICK_TO_DOWNLOAD), downloadLink);
jb.sendJson(p);
}
@ -287,16 +281,10 @@ public class ShopChest extends JavaPlugin {
debug("Registering listeners...");
getServer().getPluginManager().registerEvents(new HologramUpdateListener(this), this);
getServer().getPluginManager().registerEvents(new ItemProtectListener(this), this);
getServer().getPluginManager().registerEvents(new ShopItemListener(this), this);
getServer().getPluginManager().registerEvents(new ShopInteractListener(this), this);
getServer().getPluginManager().registerEvents(new NotifyUpdateOnJoinListener(this), this);
getServer().getPluginManager().registerEvents(new ChestProtectListener(this), this);
getServer().getPluginManager().registerEvents(new ItemCustomNameListener(), this);
if (getServer().getPluginManager().isPluginEnabled("ClearLag"))
getServer().getPluginManager().registerEvents(new ClearLagListener(), this);
if (lwc) new LWCMagnetListener(this).initializeListener();
}
@Override
@ -314,38 +302,6 @@ public class ShopChest extends JavaPlugin {
}
}
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof Item) {
Item item = (Item) entity;
if (item.hasMetadata("shopItem")) {
if (item.isValid()) {
debug("Removing not removed shop item (#" +
(item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")");
item.remove();
}
}
}
}
}
if (config.enable_debug_log) {
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof Item) {
Item item = (Item) entity;
if (item.hasMetadata("shopItem")) {
if (item.isValid()) {
debug("Shop item still valid (#" +
(item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")");
}
}
}
}
}
}
database.disconnect();
if (fw != null && config.enable_debug_log) {
@ -426,13 +382,6 @@ public class ShopChest extends JavaPlugin {
return database;
}
/**
* @return Whether LWC is available
*/
public boolean hasLWC() {
return lwc;
}
/**
* @return Whether an update is needed (will return false if not checked)
*/

View File

@ -1,25 +0,0 @@
package de.epiceric.shopchest.listeners;
import me.minebuilders.clearlag.events.EntityRemoveEvent;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import java.util.ArrayList;
public class ClearLagListener implements Listener {
@EventHandler(priority = EventPriority.HIGH)
public void onEntityRemove(EntityRemoveEvent e) {
ArrayList<Entity> entityList = new ArrayList<>(e.getEntityList());
for (Entity entity : entityList) {
if (entity.hasMetadata("shopItem")) {
e.getEntityList().remove(entity);
}
}
}
}

View File

@ -1,17 +0,0 @@
package de.epiceric.shopchest.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.ItemSpawnEvent;
public class ItemCustomNameListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onItemSpawn(ItemSpawnEvent e) {
if (e.getEntity().hasMetadata("shopItem")) {
e.getEntity().setCustomNameVisible(false);
}
}
}

View File

@ -1,134 +0,0 @@
package de.epiceric.shopchest.listeners;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.ShopUtils;
import de.epiceric.shopchest.utils.Utils;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.ItemDespawnEvent;
import org.bukkit.event.inventory.InventoryPickupItemEvent;
import org.bukkit.event.player.PlayerBucketEmptyEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import java.lang.reflect.InvocationTargetException;
public class ItemProtectListener implements Listener {
private ShopUtils shopUtils;
private ShopChest plugin;
public ItemProtectListener(ShopChest plugin) {
this.plugin = plugin;
this.shopUtils = plugin.getShopUtils();
}
@EventHandler(priority = EventPriority.HIGH)
public void onItemDespawn(ItemDespawnEvent e) {
Item item = e.getEntity();
if (item.hasMetadata("shopItem")) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerPickUpItem(PlayerPickupItemEvent e) {
if (e.getItem().hasMetadata("shopItem")) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onItemPickup(InventoryPickupItemEvent e) {
if (e.getItem().hasMetadata("shopItem")) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onBlockPlace(BlockPlaceEvent e) {
Block b = e.getBlockPlaced();
Block below = b.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onMultiBlockPlace(BlockMultiPlaceEvent e) {
for (BlockState blockState : e.getReplacedBlockStates()) {
Block below = blockState.getBlock().getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onPistonExtend(BlockPistonExtendEvent e) {
// If the piston would only move itself
Block airAfterPiston = e.getBlock().getRelative(e.getDirection());
Block belowAir = airAfterPiston.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowAir.getLocation())) {
e.setCancelled(true);
return;
}
for (Block b : e.getBlocks()) {
Block newBlock = b.getRelative(e.getDirection());
Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onPistonRetract(BlockPistonRetractEvent e) {
for (Block b : e.getBlocks()) {
Block newBlock = b.getRelative(e.getDirection());
Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onLiquidFlow(BlockFromToEvent e) {
Block b = e.getToBlock();
Block below = b.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onBucketEmpty(PlayerBucketEmptyEvent e) {
Block b = e.getBlockClicked();
if (shopUtils.isShop(b.getLocation())) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerFish(PlayerFishEvent e) {
if (e.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) {
if (e.getCaught() instanceof Item) {
Item item = (Item) e.getCaught();
if (item.hasMetadata("shopItem")) {
plugin.debug(e.getPlayer().getName() + " tried to fish a shop item");
e.setCancelled(true);
// Use some reflection to get the EntityFishingHook class so the hook can be removed...
try {
Class<?> craftFishClass = Class.forName("org.bukkit.craftbukkit." + Utils.getServerVersion() + ".entity.CraftFish");
Object craftFish = craftFishClass.cast(e.getHook());
Class<?> entityFishingHookClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityFishingHook");
Object entityFishingHook = craftFishClass.getDeclaredMethod("getHandle").invoke(craftFish);
entityFishingHookClass.getDeclaredMethod("die").invoke(entityFishingHook);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException ex) {
plugin.debug("Failed to kill fishing hook with reflection");
plugin.debug(ex);
ex.printStackTrace();
}
}
}
}
}
}

View File

@ -1,39 +0,0 @@
package de.epiceric.shopchest.listeners;
import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCMagnetPullEvent;
import de.epiceric.shopchest.ShopChest;
public class LWCMagnetListener {
private ShopChest plugin;
public LWCMagnetListener(ShopChest plugin) {
this.plugin = plugin;
}
public void initializeListener() {
try {
Class.forName("com.griefcraft.scripting.event.LWCMagnetPullEvent");
LWC.getInstance().getModuleLoader().registerModule(ShopChest.getInstance(), new JavaModule() {
@Override
public void onMagnetPull(LWCMagnetPullEvent event) {
if (event.getItem().hasMetadata("shopItem")) {
event.setCancelled(true);
}
}
});
} catch (ClassNotFoundException ex) {
plugin.debug("Using not recommended version of LWC");
plugin.getLogger().warning("Shop items can be sucked up by the magnet flag of a protected chest of LWC.");
plugin.getLogger().warning("Use 'LWC Unofficial - Entity locking' v1.7.3 or later by 'Me_Goes_RAWR' to prevent this.");
}
}
}

View File

@ -27,7 +27,7 @@ public class NotifyUpdateOnJoinListener implements Listener {
Player p = e.getPlayer();
if (plugin.isUpdateNeeded()) {
if (p.isOp() || perm.has(p, "shopchest.notification.update")) {
if (perm.has(p, "shopchest.notification.update")) {
JsonBuilder jb = new JsonBuilder(plugin, LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_AVAILABLE, new LocalizedMessage.ReplacedRegex(Regex.VERSION, plugin.getLatestVersion())), LanguageUtils.getMessage(LocalizedMessage.Message.UPDATE_CLICK_TO_DOWNLOAD), plugin.getDownloadLink());
jb.sendJson(p);
}

View File

@ -0,0 +1,126 @@
package de.epiceric.shopchest.listeners;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.shop.Shop;
import de.epiceric.shopchest.utils.ShopUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
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.*;
import org.bukkit.event.player.*;
import java.lang.reflect.InvocationTargetException;
public class ShopItemListener implements Listener {
private ShopUtils shopUtils;
private ShopChest plugin;
public ShopItemListener(ShopChest plugin) {
this.plugin = plugin;
this.shopUtils = plugin.getShopUtils();
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent e) {
for (Shop shop : plugin.getShopUtils().getShops()) {
shop.getItem().setVisible(e.getPlayer(), true);
}
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent e) {
for (Shop shop : plugin.getShopUtils().getShops()) {
shop.getItem().setVisible(e.getPlayer(), false);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onBlockPlace(BlockPlaceEvent e) {
Block b = e.getBlockPlaced();
Block below = b.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) {
shopUtils.getShop(below.getLocation()).getItem().resetForPlayer(e.getPlayer());
e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onMultiBlockPlace(BlockMultiPlaceEvent e) {
for (BlockState blockState : e.getReplacedBlockStates()) {
Block below = blockState.getBlock().getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) {
shopUtils.getShop(below.getLocation()).getItem().resetForPlayer(e.getPlayer());
e.setCancelled(true);
}
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onPistonExtend(BlockPistonExtendEvent e) {
// If the piston would only move itself
Block airAfterPiston = e.getBlock().getRelative(e.getDirection());
Block belowAir = airAfterPiston.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowAir.getLocation())) {
e.setCancelled(true);
return;
}
for (Block b : e.getBlocks()) {
Block newBlock = b.getRelative(e.getDirection());
Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowNewBlock.getLocation())) e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onPistonRetract(BlockPistonRetractEvent e) {
for (Block b : e.getBlocks()) {
Block newBlock = b.getRelative(e.getDirection());
Block belowNewBlock = newBlock.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(belowNewBlock.getLocation())) {
e.setCancelled(true);
for (Player p : Bukkit.getOnlinePlayers()) {
shopUtils.getShop(belowNewBlock.getLocation()).getItem().resetForPlayer(p);
}
}
}
}
@EventHandler(priority = EventPriority.HIGH)
public void onLiquidFlow(BlockFromToEvent e) {
Block b = e.getToBlock();
Block below = b.getRelative(BlockFace.DOWN);
if (shopUtils.isShop(below.getLocation())) e.setCancelled(true);
}
@EventHandler(priority = EventPriority.HIGH)
public void onBucketEmpty(PlayerBucketEmptyEvent e) {
Block clicked = e.getBlockClicked();
Block underWater = clicked.getRelative(BlockFace.DOWN).getRelative(e.getBlockFace());
if (shopUtils.isShop(clicked.getLocation())) {
if (e.getBucket() == Material.LAVA_BUCKET) {
shopUtils.getShop(clicked.getLocation()).getItem().resetForPlayer(e.getPlayer());
}
} else if (shopUtils.isShop(underWater.getLocation())) {
if (e.getBucket() == Material.LAVA_BUCKET) {
shopUtils.getShop(underWater.getLocation()).getItem().resetForPlayer(e.getPlayer());
}
} else {
return;
}
e.setCancelled(true);
}
}

View File

@ -13,7 +13,6 @@ import java.util.List;
public class Hologram {
private Class<?> entityArmorStandClass;
private boolean exists = false;
private int count;
private List<Object> entityList = new ArrayList<>();
@ -22,19 +21,28 @@ public class Hologram {
private List<Player> visible = new ArrayList<>();
private ShopChest plugin;
private Class<?> entityArmorStandClass = Utils.getNMSClass("EntityArmorStand");
private Class<?> nmsWorldClass = Utils.getNMSClass("World");
private Class<?> packetPlayOutSpawnEntityLivingClass = Utils.getNMSClass("PacketPlayOutSpawnEntityLiving");
private Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
private Class<?> entityLivingClass = Utils.getNMSClass("EntityLiving");
public Hologram(ShopChest plugin, String[] text, Location location) {
this.plugin = plugin;
this.text = text;
this.location = location;
try {
entityArmorStandClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityArmorStand");
} catch (ClassNotFoundException e) {
plugin.debug("Could not find EntityArmorStand class");
plugin.debug(e);
e.printStackTrace();
Class[] requiredClasses = new Class[] {
nmsWorldClass, entityArmorStandClass, entityLivingClass,
packetPlayOutSpawnEntityLivingClass, packetPlayOutEntityDestroyClass,
};
for (Class c : requiredClasses) {
if (c == null) {
plugin.debug("Failed to create hologram: Could not find all required classes");
return;
}
}
create();
}
@ -42,8 +50,6 @@ public class Hologram {
private void create() {
for (String text : this.text) {
try {
Class<?> nmsWorldClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".World");
Object craftWorld = this.location.getWorld().getClass().cast(this.location.getWorld());
Object nmsWorld = nmsWorldClass.cast(craftWorld.getClass().getMethod("getHandle").invoke(craftWorld));
@ -63,7 +69,7 @@ public class Hologram {
entityList.add(entityArmorStand);
this.location.subtract(0, 0.25, 0);
count++;
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
plugin.debug("Could not create Hologram with reflection");
plugin.debug(e);
e.printStackTrace();
@ -93,14 +99,11 @@ public class Hologram {
public void showPlayer(Player p) {
for (Object o : entityList) {
try {
Class<?> packetClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".PacketPlayOutSpawnEntityLiving");
Class<?> entityLivingClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".EntityLiving");
Object entityLiving = entityLivingClass.cast(o);
Object packet = packetClass.getConstructor(entityLivingClass).newInstance(entityLiving);
Object packet = packetPlayOutSpawnEntityLivingClass.getConstructor(entityLivingClass).newInstance(entityLiving);
Utils.sendPacket(plugin, packet, p);
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException | ClassNotFoundException e) {
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) {
plugin.debug("Could not show Hologram to player with reflection");
plugin.debug(e);
e.printStackTrace();
@ -115,14 +118,12 @@ public class Hologram {
public void hidePlayer(Player p) {
for (Object o : entityList) {
try {
Class<?> packetClass = Class.forName("net.minecraft.server." + Utils.getServerVersion() + ".PacketPlayOutEntityDestroy");
int id = (int) entityArmorStandClass.getMethod("getId").invoke(o);
Object packet = packetClass.getConstructor(int[].class).newInstance((Object) new int[] {id});
Object packet = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[] {id});
Utils.sendPacket(plugin, packet, p);
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException | ClassNotFoundException e) {
} catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) {
plugin.debug("Could not hide Hologram from player with reflection");
plugin.debug(e);
e.printStackTrace();

View File

@ -15,9 +15,30 @@ public class JsonBuilder {
private List<String> extras = new ArrayList<>();
private ShopChest plugin;
private Class<?> iChatBaseComponentClass = Utils.getNMSClass("IChatBaseComponent");
private Class<?> packetPlayOutChatClass = Utils.getNMSClass("PacketPlayOutChat");
private Class<?> chatSerializerClass;
public JsonBuilder(ShopChest plugin, String text, String hoverText, String downloadLink) {
this.plugin = plugin;
if (Utils.getServerVersion().equals("v1_8_R1")) {
chatSerializerClass = Utils.getNMSClass("ChatSerializer");
} else {
chatSerializerClass = Utils.getNMSClass("ChatBaseComponent$ChatSerializer");
}
Class[] requiredClasses = new Class[] {
iChatBaseComponentClass, packetPlayOutChatClass, chatSerializerClass
};
for (Class c : requiredClasses) {
if (c == null) {
plugin.debug("Failed to instantiate JsonBuilder: Could not find all required classes");
return;
}
}
parse(text, hoverText, downloadLink);
}
@ -90,24 +111,12 @@ public class JsonBuilder {
public void sendJson(Player p) {
try {
String version = Utils.getServerVersion();
Class<?> iChatBaseComponentClass = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent");
Class<?> packetPlayOutChatClass = Class.forName("net.minecraft.server." + version + ".PacketPlayOutChat");
Class<?> chatSerializerClass;
if (version.equals("v1_8_R1")) {
chatSerializerClass = Class.forName("net.minecraft.server." + version + ".ChatSerializer");
} else {
chatSerializerClass = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer");
}
Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, toString());
Object packetPlayOutChat = packetPlayOutChatClass.getConstructor(iChatBaseComponentClass).newInstance(iChatBaseComponent);
Utils.sendPacket(plugin, packetPlayOutChat, p);
} catch (InstantiationException | InvocationTargetException |
IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
IllegalAccessException | NoSuchMethodException e) {
plugin.debug("Failed to send packet with reflection");
plugin.debug(e);
e.printStackTrace();

View File

@ -11,7 +11,12 @@ public class SpawnEggMeta {
private static String getNBTEntityID(ShopChest plugin, ItemStack stack) {
try {
Class<?> craftItemStackClass = Class.forName("org.bukkit.craftbukkit." + Utils.getServerVersion() + ".inventory.CraftItemStack");
Class<?> craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack");
if (craftItemStackClass == null) {
plugin.debug("Failed to get NBTEntityID: Could not find CraftItemStack class");
return null;
}
Object nmsStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, stack);
@ -24,7 +29,7 @@ public class SpawnEggMeta {
Object id = entityTagCompound.getClass().getMethod("getString", String.class).invoke(entityTagCompound, "id");
if (id instanceof String) return (String) id;
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
plugin.debug("Could not get NBTEntityID with reflection");
plugin.debug(e);
e.printStackTrace();

View File

@ -5,7 +5,6 @@ import de.epiceric.shopchest.config.Regex;
import de.epiceric.shopchest.language.LanguageUtils;
import de.epiceric.shopchest.language.LocalizedMessage;
import de.epiceric.shopchest.nms.Hologram;
import de.epiceric.shopchest.utils.Utils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@ -14,15 +13,9 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Chest;
import org.bukkit.block.DoubleChest;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.util.Vector;
import java.util.UUID;
public class Shop {
@ -32,7 +25,7 @@ public class Shop {
private ItemStack product;
private Location location;
private Hologram hologram;
private Item item;
private ShopItem item;
private double buyPrice;
private double sellPrice;
private ShopType shopType;
@ -71,7 +64,7 @@ public class Shop {
}
if (hologram == null || !hologram.exists()) createHologram();
if (item == null || item.isDead()) createItem();
if (item == null) createItem();
}
private Shop(OfflinePlayer vendor, ItemStack product, Location location, double buyPrice, double sellPrice, ShopType shopType) {
@ -117,25 +110,18 @@ public class Shop {
if (plugin.getShopChestConfig().show_shop_items) {
plugin.debug("Creating item (#" + id + ")");
Item item;
Location itemLocation;
ItemStack itemStack;
ItemMeta itemMeta = product.getItemMeta();
itemMeta.setDisplayName(UUID.randomUUID().toString());
itemLocation = new Location(location.getWorld(), hologram.getLocation().getX(), location.getY() + 1, hologram.getLocation().getZ());
itemStack = new ItemStack(product);
itemStack = product.clone();
itemStack.setAmount(1);
itemStack.setItemMeta(itemMeta);
item = location.getWorld().dropItem(itemLocation, itemStack);
item.setVelocity(new Vector(0, 0, 0));
item.setMetadata("shopItem", new FixedMetadataValue(plugin, true));
item.setMetadata("shopId", new FixedMetadataValue(plugin, id));
item.setCustomNameVisible(false);
item.setPickupDelay(Integer.MAX_VALUE);
this.item = new ShopItem(plugin, itemStack, itemLocation);
this.item = item;
for (Player p : Bukkit.getOnlinePlayers()) {
item.setVisible(p, true);
}
}
}
@ -269,6 +255,13 @@ public class Shop {
return hologram;
}
/**
* @return Floating {@link ShopItem} of the shop
*/
public ShopItem getItem() {
return item;
}
/**
* @return {@link InventoryHolder} of the shop or <b>null</b> if the shop has no chest.
*/

View File

@ -0,0 +1,155 @@
package de.epiceric.shopchest.shop;
import de.epiceric.shopchest.ShopChest;
import de.epiceric.shopchest.utils.Utils;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class ShopItem {
private ShopChest plugin;
private Map<Player, Boolean> visible = new HashMap<>();
private ItemStack itemStack;
private Location location;
private Object entityItem;
private int entityId;
private Object[] creationPackets = new Object[3];
private Class<?> nmsWorldClass = Utils.getNMSClass("World");
private Class<?> craftWorldClass = Utils.getCraftClass("CraftWorld");
private Class<?> nmsItemStackClass = Utils.getNMSClass("ItemStack");
private Class<?> craftItemStackClass = Utils.getCraftClass("inventory.CraftItemStack");
private Class<?> entityItemClass = Utils.getNMSClass("EntityItem");
private Class<?> entityClass = Utils.getNMSClass("Entity");
private Class<?> packetPlayOutSpawnEntityClass = Utils.getNMSClass("PacketPlayOutSpawnEntity");
private Class<?> packetPlayOutEntityMetadataClass = Utils.getNMSClass("PacketPlayOutEntityMetadata");
private Class<?> dataWatcherClass = Utils.getNMSClass("DataWatcher");
private Class<?> packetPlayOutEntityDestroyClass = Utils.getNMSClass("PacketPlayOutEntityDestroy");
private Class<?> packetPlayOutEntityVelocityClass = Utils.getNMSClass("PacketPlayOutEntityVelocity");
public ShopItem(ShopChest plugin, ItemStack itemStack, Location location) {
this.plugin = plugin;
this.itemStack = itemStack;
this.location = location;
Class[] requiredClasses = new Class[] {
nmsWorldClass, craftWorldClass, nmsItemStackClass, craftItemStackClass, entityItemClass,
packetPlayOutSpawnEntityClass, packetPlayOutEntityMetadataClass, dataWatcherClass,
packetPlayOutEntityDestroyClass, entityClass, packetPlayOutEntityVelocityClass,
};
for (Class c : requiredClasses) {
if (c == null) {
plugin.debug("Failed to create shop item: Could not find all required classes");
return;
}
}
create();
}
private void create() {
try {
Object craftWorld = craftWorldClass.cast(location.getWorld());
Object nmsWorld = craftWorldClass.getMethod("getHandle").invoke(craftWorld);
Object nmsItemStack = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class).invoke(null, itemStack);
Constructor<?> entityItemConstructor = entityItemClass.getConstructor(nmsWorldClass);
entityItem = entityItemConstructor.newInstance(nmsWorld);
entityItemClass.getMethod("setPosition", double.class, double.class, double.class).invoke(entityItem, location.getX(), location.getY(), location.getZ());
entityItemClass.getMethod("setItemStack", nmsItemStackClass).invoke(entityItem, nmsItemStack);
Field ageField = entityItemClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.setInt(entityItem, -32768);
entityId = (int) entityItemClass.getMethod("getId").invoke(entityItem);
Object dataWatcher = entityItemClass.getMethod("getDataWatcher").invoke(entityItem);
creationPackets[0] = packetPlayOutSpawnEntityClass.getConstructor(entityClass, int.class).newInstance(entityItem, 2);
creationPackets[1] = packetPlayOutEntityMetadataClass.getConstructor(int.class, dataWatcherClass, boolean.class).newInstance(entityId, dataWatcher, true);
creationPackets[2] = packetPlayOutEntityVelocityClass.getConstructor(int.class, double.class, double.class, double.class).newInstance(entityId, 0D, 0D, 0D);
} catch (NoSuchMethodException | NoSuchFieldException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
plugin.debug("Failed to create shop item with reflection");
plugin.debug(e);
e.printStackTrace();
}
}
public void remove() {
for (Player p : visible.keySet()) {
if (isVisible(p)) setVisible(p, false);
}
}
/**
* Respawns the item at the set location for a player
* @param p Player, for which the item should be reset
*/
public void resetForPlayer(Player p) {
setVisible(p, false);
setVisible(p, true);
}
public boolean isVisible(Player p) {
return visible.get(p) == null ? false : visible.get(p);
}
public void setVisible(final Player p, boolean visible) {
if (this.visible.containsKey(p) && this.visible.get(p) == visible)
return;
if (visible) {
for (Object packet : this.creationPackets) {
Utils.sendPacket(plugin, packet, p);
}
} else {
try {
if (p.isOnline()) {
Object packetPlayOutEntityDestroy = packetPlayOutEntityDestroyClass.getConstructor(int[].class).newInstance((Object) new int[]{entityId});
Utils.sendPacket(plugin, packetPlayOutEntityDestroy, p);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
plugin.debug("Failed to destroy shop item with reflection");
plugin.debug(e);
e.printStackTrace();
}
}
this.visible.put(p, visible);
}
/**
* @return Clone of the location, where the shop item should be (it could have been moved by something, even though it shouldn't)
* To get the exact location, use reflection and extract the location of the {@code EntityItem}
* which you can get in {@link #getEntityItem()}.
*/
public Location getLocation() {
return location.clone();
}
/**
* @return {@code net.minecraft.server.[VERSION].EntityItem}
*/
public Object getEntityItem() {
return entityItem;
}
/**
* @return A clone of this Item's {@link ItemStack}
*/
public ItemStack getItemStack() {
return itemStack.clone();
}
}

View File

@ -221,36 +221,6 @@ public class ShopUtils {
}
}
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof Item) {
Item item = (Item) entity;
if (item.hasMetadata("shopItem")) {
if (item.isValid()) {
plugin.debug("Removing not removed shop item (#" +
(item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")");
item.remove();
}
}
}
}
}
for (World world : Bukkit.getWorlds()) {
for (Entity entity : world.getEntities()) {
if (entity instanceof Item) {
Item item = (Item) entity;
if (item.hasMetadata("shopItem")) {
if (item.isValid()) {
plugin.debug("Shop item still valid (#" +
(item.hasMetadata("shopId") ? item.getMetadata("shopId").get(0).asString() : "?") + ")");
}
}
}
}
}
int count = 0;
for (int id = 1; id <= highestId; id++) {

View File

@ -53,6 +53,30 @@ public class Utils {
return amount;
}
/**
* @param className Name of the class
* @return Class in {@code net.minecraft.server.[VERSION]} package with the specified name or {@code null} if the class was not found
*/
public static Class<?> getNMSClass(String className) {
try {
return Class.forName("net.minecraft.server." + getServerVersion() + "." + className);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* @param className Name of the class
* @return Class in {@code org.bukkit.craftbukkit.[VERSION]} package with the specified name or {@code null} if the class was not found
*/
public static Class<?> getCraftClass(String className) {
try {
return Class.forName("org.bukkit.craftbukkit." + getServerVersion() + "." + className);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Send a packet to a player
* @param packet Packet to send