diff --git a/lib/ProtocolLib-3.6.5-SNAPSHOT.jar b/lib/ProtocolLib-3.6.5-SNAPSHOT.jar new file mode 100644 index 0000000..ec73d32 Binary files /dev/null and b/lib/ProtocolLib-3.6.5-SNAPSHOT.jar differ diff --git a/pom.xml b/pom.xml index 0373556..9a1eef8 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,8 @@ http://ci.citycraft.cn:8080 DEBUG - &a修复手动处理无效的问题 使用ProtocolLib序列化物品(&c感谢尘曲修复类库&a)... + &a修复手动处理无效的问题 + 使用ProtocolLib序列化物品(&c感谢尘曲修复类库&a)... UTF-8 @@ -89,6 +90,13 @@ system ${project.basedir}/lib/WorldEdit.jar + + com.comphenix.protocol + ProtocolLib + 3.6.5-SNAPSHOT + system + ${project.basedir}/lib/ProtocolLib-3.6.5-SNAPSHOT.jar + cn.citycraft PluginHelper diff --git a/src/main/java/org/maxgamer/QuickShop/Config/ConfigManager.java b/src/main/java/org/maxgamer/QuickShop/Config/ConfigManager.java index 38bdd4b..87012e2 100644 --- a/src/main/java/org/maxgamer/QuickShop/Config/ConfigManager.java +++ b/src/main/java/org/maxgamer/QuickShop/Config/ConfigManager.java @@ -7,6 +7,7 @@ import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; import org.maxgamer.QuickShop.QuickShop; +import org.maxgamer.QuickShop.Shop.FakeItem; import cn.citycraft.PluginHelper.config.FileConfig; import cn.citycraft.PluginHelper.tellraw.FancyMessage; @@ -44,6 +45,8 @@ public class ConfigManager { private Material superItem = Material.GOLD_AXE; private double tax = 0; private final String taxAccount; + private boolean fakeItem = false; + /** * A set of players who have been warned * ("Your shop isn't automatically locked") @@ -79,6 +82,19 @@ public class ConfigManager { this.feeForPriceChange = config.getDouble("shop.fee-for-price-change"); this.preventhopper = config.getBoolean("preventhopper"); this.guiTitle = config.getMessage("guititle", guiTitle); + this.fakeItem = config.getBoolean("fakeitem", true); + if (config.getBoolean("fakeitem", true)) { + try { + plugin.getLogger().info("启用虚拟悬浮物 尝试启动中..."); + FakeItem.register(plugin); + plugin.getLogger().info("虚拟悬浮物功能测试正常..."); + fakeItem = true; + } catch (final Exception e) { + plugin.getLogger().warning("+========================================="); + plugin.getLogger().warning("| 警告: 虚拟物品启动失败 使用原版悬浮物品..."); + plugin.getLogger().warning("+========================================="); + } + } if (config.getBoolean("usemagiclib", true)) { try { plugin.getLogger().info("启用魔改库 尝试启动中..."); @@ -145,6 +161,10 @@ public class ConfigManager { return enableMagicLib; } + public boolean isFakeItem() { + return fakeItem; + } + public boolean isLimit() { return limit; } @@ -185,4 +205,8 @@ public class ConfigManager { this.enableMagicLib = enableMagicLib; } + public void setFakeItem(final boolean fakeItem) { + this.fakeItem = fakeItem; + } + } diff --git a/src/main/java/org/maxgamer/QuickShop/QuickShop.java b/src/main/java/org/maxgamer/QuickShop/QuickShop.java index 8e6fd6d..b6686c7 100644 --- a/src/main/java/org/maxgamer/QuickShop/QuickShop.java +++ b/src/main/java/org/maxgamer/QuickShop/QuickShop.java @@ -43,6 +43,7 @@ import org.maxgamer.QuickShop.Listeners.ProtectListener; import org.maxgamer.QuickShop.Listeners.WorldListener; import org.maxgamer.QuickShop.Listeners.WowSuchCleanerListener; import org.maxgamer.QuickShop.Shop.ContainerShop; +import org.maxgamer.QuickShop.Shop.FakeItem; import org.maxgamer.QuickShop.Shop.Shop; import org.maxgamer.QuickShop.Shop.ShopManager; import org.maxgamer.QuickShop.Shop.ShopType; @@ -295,6 +296,14 @@ public class QuickShop extends JavaPlugin { // Create the shop manager. configManager = new ConfigManager(this); shopManager = new ShopManager(this); + if (configManager.isFakeItem()) { + if (!FakeItem.isRegistered()) { + try { + FakeItem.register(this); + } catch (final Exception e) { + } + } + } if (configManager.isLogAction()) { // Logger Handler this.logWatcher = new LogWatcher(this, new File(this.getDataFolder(), "qs.log")); diff --git a/src/main/java/org/maxgamer/QuickShop/Shop/ContainerShop.java b/src/main/java/org/maxgamer/QuickShop/Shop/ContainerShop.java index d981adf..f9de264 100644 --- a/src/main/java/org/maxgamer/QuickShop/Shop/ContainerShop.java +++ b/src/main/java/org/maxgamer/QuickShop/Shop/ContainerShop.java @@ -23,7 +23,7 @@ import org.maxgamer.QuickShop.Util.MsgUtil; import org.maxgamer.QuickShop.Util.Util; public class ContainerShop implements Shop { - private DisplayItem displayItem; + private DisplayItem displayName; private final ItemStack item; private final Location loc; private String owner; @@ -53,13 +53,17 @@ public class ContainerShop implements Shop { this.plugin = (QuickShop) Bukkit.getPluginManager().getPlugin("QuickShop"); this.item.setAmount(1); if (plugin.getConfigManager().isDisplay()) { - this.displayItem = new DisplayItem(this, this.item); + if (plugin.getConfigManager().isFakeItem()) { + this.displayName = new FakeItem(this, this.getItem()); + } else { + this.displayName = new NormalItem(this, this.getItem()); + } } this.shopType = ShopType.SELLING; } private ContainerShop(final ContainerShop s) { - this.displayItem = s.displayItem; + this.displayName = s.displayName; this.shopType = s.shopType; this.item = s.item; this.loc = s.loc; @@ -243,7 +247,7 @@ public class ContainerShop implements Shop { * @return The display item associated with this shop. */ public DisplayItem getDisplayItem() { - return this.displayItem; + return this.displayName; } /** @@ -469,7 +473,7 @@ public class ContainerShop implements Shop { public void onUnload() { if (this.getDisplayItem() != null) { this.getDisplayItem().remove(); - this.displayItem = null; + this.displayName = null; } } @@ -677,14 +681,18 @@ public class ContainerShop implements Shop { } final boolean trans = Util.isTransparent(getLocation().clone().add(0.5, 1.2, 0.5).getBlock().getType()); if (trans && this.getDisplayItem() == null) { - this.displayItem = new DisplayItem(this, this.getItem()); + if (plugin.getConfigManager().isFakeItem()) { + this.displayName = new FakeItem(this, this.getItem()); + } else { + this.displayName = new NormalItem(this, this.getItem()); + } this.getDisplayItem().spawn(); return; } if (this.getDisplayItem() != null) { if (!trans) { // We have a display item in a block... delete it this.getDisplayItem().remove(); - this.displayItem = null; + this.displayName = null; return; } final DisplayItem disItem = this.getDisplayItem(); diff --git a/src/main/java/org/maxgamer/QuickShop/Shop/DisplayItem.java b/src/main/java/org/maxgamer/QuickShop/Shop/DisplayItem.java index 8bc0149..99f0e94 100644 --- a/src/main/java/org/maxgamer/QuickShop/Shop/DisplayItem.java +++ b/src/main/java/org/maxgamer/QuickShop/Shop/DisplayItem.java @@ -1,110 +1,44 @@ package org.maxgamer.QuickShop.Shop; -import org.bukkit.Chunk; import org.bukkit.Location; -import org.bukkit.entity.Entity; import org.bukkit.entity.Item; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; -import org.maxgamer.QuickShop.Util.NMS; /** * @author Netherfoam A display item, that spawns a block above the chest and * cannot be interacted with. */ -public class DisplayItem { - private final ItemStack iStack; - private Item item; - private final Shop shop; - - // private Location displayLoc; +public interface DisplayItem { /** - * Creates a new display item. + * 获得悬浮物地点 * - * @param shop - * The shop (See Shop) - * @param iStack - * The item stack to clone properties of the display item from. + * @return 获得悬浮地点 */ - public DisplayItem(final Shop shop, final ItemStack iStack) { - this.shop = shop; - this.iStack = iStack.clone(); - // this.displayLoc = shop.getLocation().clone().add(0.5, 1.2, 0.5); - } + public Location getDisplayLocation(); /** - * @return Returns the exact location of the display item. (1 above shop - * block, in the center) + * @return {@link Item} */ - public Location getDisplayLocation() { - return this.shop.getLocation().clone().add(0.5, 1.2, 0.5); - } + public Item getItem(); /** - * Returns the reference to this shops item. Do not modify. + * 移除悬浮物 */ - public Item getItem() { - return this.item; - } + public void remove(); /** - * Removes the display item. + * 移除多余物品 + * + * @return */ - public void remove() { - if (this.item == null) { - return; - } - this.item.remove(); - } + public boolean removeDupe(); /** - * Removes all items floating ontop of the chest that aren't the display - * item. + * 更新悬浮物 */ - public boolean removeDupe() { - if (shop.getLocation().getWorld() == null) { - return false; - } - final Location displayLoc = shop.getLocation().getBlock().getRelative(0, 1, 0).getLocation(); - boolean removed = false; - final Chunk c = displayLoc.getChunk(); - for (final Entity e : c.getEntities()) { - if (!(e instanceof Item)) { - continue; - } - if (this.item != null && e.getEntityId() == this.item.getEntityId()) { - continue; - } - final Location eLoc = e.getLocation().getBlock().getLocation(); - if (eLoc.equals(displayLoc) || eLoc.equals(shop.getLocation())) { - e.remove(); - removed = true; - } - } - return removed; - } + public void respawn(); /** - * Spawns the new display item. Does not remove duplicate items. + * 刷出悬浮物 */ - public void respawn() { - remove(); - spawn(); - } - - /** - * Spawns the dummy item on top of the shop. - */ - public void spawn() { - if (shop.getLocation().getWorld() == null) { - return; - } - final Location dispLoc = this.getDisplayLocation(); - try { - this.item = shop.getLocation().getWorld().dropItem(dispLoc, this.iStack); - this.item.setVelocity(new Vector(0, 0.1, 0)); - NMS.safeGuard(this.item); - } catch (final Exception e) { - } - } + public void spawn(); } \ No newline at end of file diff --git a/src/main/java/org/maxgamer/QuickShop/Shop/FakeItem.java b/src/main/java/org/maxgamer/QuickShop/Shop/FakeItem.java new file mode 100644 index 0000000..b97f48e --- /dev/null +++ b/src/main/java/org/maxgamer/QuickShop/Shop/FakeItem.java @@ -0,0 +1,238 @@ +package org.maxgamer.QuickShop.Shop; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; + +/** + * Minecraft 虚拟悬浮物品工具类 + * 需要depend ProtocolLib + * + * @author 橙子(chengzi) + * @version 1.0.1 + */ +public class FakeItem implements DisplayItem { + + private static Map> fakes = new HashMap>(); + private static boolean registered = false; + private static int lastId = Integer.MAX_VALUE; + + private final ItemStack itemStack; + private final Location location; + private final int eid; + private boolean created = false; + + public static boolean isRegistered() { + return registered; + } + + public static void register(final Plugin plugin) { + if (registered) { + return; + } + final PluginManager pm = Bukkit.getPluginManager(); + final Plugin p = pm.getPlugin("ProtocolLib"); + if (p != null) { + if (!p.isEnabled()) { + pm.enablePlugin(p); + } + if (!p.isEnabled()) { + throw new IllegalStateException("The ProtocolLib enable Failed."); + } + } else { + throw new IllegalStateException("The Server Not Found ProtocolLib."); + } + final PacketAdapter chunkPacketListener = new PacketAdapter(plugin, PacketType.Play.Server.MAP_CHUNK) { + @Override + public void onPacketSending(final PacketEvent event) { + final PacketContainer packet = event.getPacket(); + final Player p = event.getPlayer(); + final int chunkX = packet.getIntegers().read(0); + final int chunkZ = packet.getIntegers().read(1); + final List fakesInChunk = fakes.get(getChunkIdentifyString(p.getWorld().getChunkAt(chunkX, chunkZ))); + if (fakesInChunk != null) { + try { + for (final FakeItem fake : fakesInChunk) { + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getSpawnPacket()); + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getVelocityPacket()); + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getMetadataPacket()); + } + } catch (final InvocationTargetException e) { + } + } + } + }; + final PacketAdapter chunkBulkPacketListener = new PacketAdapter(plugin, PacketType.Play.Server.MAP_CHUNK_BULK) { + @Override + public void onPacketSending(final PacketEvent event) { + final PacketContainer packet = event.getPacket(); + final Player p = event.getPlayer(); + final int[] chunksX = packet.getIntegerArrays().read(0); + final int[] chunksZ = packet.getIntegerArrays().read(1); + for (int i = 0; i < chunksX.length; i++) { + final List fakesInChunk = fakes.get(getChunkIdentifyString(p.getWorld().getChunkAt(chunksX[i], chunksZ[i]))); + if (fakesInChunk != null) { + try { + for (final FakeItem fake : fakesInChunk) { + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getSpawnPacket()); + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getVelocityPacket()); + ProtocolLibrary.getProtocolManager().sendServerPacket(p, fake.getMetadataPacket()); + } + } catch (final InvocationTargetException e) { + } + } + } + } + }; + + ProtocolLibrary.getProtocolManager().addPacketListener(chunkPacketListener); + ProtocolLibrary.getProtocolManager().addPacketListener(chunkBulkPacketListener); + registered = true; + } + + private static String getChunkIdentifyString(final Chunk chunk) { + return chunk.getWorld().getName() + "@@" + chunk.getX() + "@@" + chunk.getZ(); + } + + private static int getFakeEntityId() { + return lastId--; + } + + private static int getNormalizedDistance(final double value) { + return (int) Math.floor(value * 32.0D); + } + + public FakeItem(final ContainerShop containerShop, final ItemStack item) { + this.itemStack = item; + this.location = containerShop.getLocation().add(0.5, 1, 0.5); + this.eid = getFakeEntityId(); + } + + public FakeItem(final ItemStack itemStack, final Location loc) { + this.itemStack = itemStack; + this.location = loc; + this.eid = getFakeEntityId(); + } + + @Override + public Location getDisplayLocation() { + return location; + } + + @Override + public Item getItem() { + return null; + } + + @Override + public void remove() { + destory(); + } + + @Override + public boolean removeDupe() { + return true; + } + + @Override + public void respawn() { + destory(); + create(); + } + + @Override + public void spawn() { + create(); + } + + private void create() { + if (!registered) { + throw new IllegalStateException("You have to call the register method first."); + } + if (created) { + return; + } + ProtocolLibrary.getProtocolManager().broadcastServerPacket(getSpawnPacket()); + ProtocolLibrary.getProtocolManager().broadcastServerPacket(getVelocityPacket()); + ProtocolLibrary.getProtocolManager().broadcastServerPacket(getMetadataPacket()); + + final String chunkId = getChunkIdentifyString(location.getChunk()); + List fakesInChunk = fakes.get(chunkId); + if (fakesInChunk == null) { + fakesInChunk = new ArrayList(); + } + fakesInChunk.add(this); + fakes.put(chunkId, fakesInChunk); + created = true; + } + + private void destory() { + if (!created) { + return; + } + ProtocolLibrary.getProtocolManager().broadcastServerPacket(getDestoryPacket()); + + final String chunkId = getChunkIdentifyString(location.getChunk()); + final List fakesInChunk = fakes.get(chunkId); + if (fakesInChunk == null) { + // NOTE: This is what should not happens if everything is correct. + created = false; + return; + } + fakesInChunk.remove(this); + fakes.put(chunkId, fakesInChunk); + created = false; + } + + private PacketContainer getDestoryPacket() { + final PacketContainer fakePacket = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.ENTITY_DESTROY); + fakePacket.getIntegers().write(0, 1); + fakePacket.getIntegerArrays().write(0, new int[] { eid }); + return fakePacket; + } + + private PacketContainer getMetadataPacket() { + final PacketContainer fakePacket = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.ENTITY_METADATA); + fakePacket.getIntegers().write(0, eid); + final WrappedWatchableObject itemMeta = new WrappedWatchableObject(10, itemStack); + final List entityMetaList = new ArrayList(1); + entityMetaList.add(itemMeta); + fakePacket.getWatchableCollectionModifier().write(0, entityMetaList); + return fakePacket; + } + + private PacketContainer getSpawnPacket() { + final PacketContainer fakePacket = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.SPAWN_ENTITY); + fakePacket.getIntegers().write(0, eid); + fakePacket.getIntegers().write(1, getNormalizedDistance(location.getX())); + fakePacket.getIntegers().write(2, getNormalizedDistance(location.getY())); + fakePacket.getIntegers().write(3, getNormalizedDistance(location.getZ())); + fakePacket.getIntegers().write(9, 2); + return fakePacket; + } + + private PacketContainer getVelocityPacket() { + final PacketContainer fakePacket = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.ENTITY_VELOCITY); + fakePacket.getIntegers().write(0, eid); + return fakePacket; + } + +} diff --git a/src/main/java/org/maxgamer/QuickShop/Shop/NormalItem.java b/src/main/java/org/maxgamer/QuickShop/Shop/NormalItem.java new file mode 100644 index 0000000..274406e --- /dev/null +++ b/src/main/java/org/maxgamer/QuickShop/Shop/NormalItem.java @@ -0,0 +1,116 @@ +package org.maxgamer.QuickShop.Shop; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.maxgamer.QuickShop.Util.NMS; + +/** + * @author Netherfoam A display item, that spawns a block above the chest and + * cannot be interacted with. + */ +public class NormalItem implements DisplayItem { + private final ItemStack iStack; + private Item item; + private final Shop shop; + + // private Location displayLoc; + /** + * Creates a new display item. + * + * @param shop + * The shop (See Shop) + * @param iStack + * The item stack to clone properties of the display item from. + */ + public NormalItem(final Shop shop, final ItemStack iStack) { + this.shop = shop; + this.iStack = iStack.clone(); + // this.displayLoc = shop.getLocation().clone().add(0.5, 1.2, 0.5); + } + + /** + * @return Returns the exact location of the display item. (1 above shop + * block, in the center) + */ + @Override + public Location getDisplayLocation() { + return this.shop.getLocation().clone().add(0.5, 1.2, 0.5); + } + + /** + * Returns the reference to this shops item. Do not modify. + */ + @Override + public Item getItem() { + return this.item; + } + + /** + * Removes the display item. + */ + @Override + public void remove() { + if (this.item == null) { + return; + } + this.item.remove(); + } + + /** + * Removes all items floating ontop of the chest that aren't the display + * item. + */ + @Override + public boolean removeDupe() { + if (shop.getLocation().getWorld() == null) { + return false; + } + final Location displayLoc = shop.getLocation().getBlock().getRelative(0, 1, 0).getLocation(); + boolean removed = false; + final Chunk c = displayLoc.getChunk(); + for (final Entity e : c.getEntities()) { + if (!(e instanceof Item)) { + continue; + } + if (this.item != null && e.getEntityId() == this.item.getEntityId()) { + continue; + } + final Location eLoc = e.getLocation().getBlock().getLocation(); + if (eLoc.equals(displayLoc) || eLoc.equals(shop.getLocation())) { + e.remove(); + removed = true; + } + } + return removed; + } + + /** + * Spawns the new display item. Does not remove duplicate items. + */ + @Override + public void respawn() { + remove(); + spawn(); + } + + /** + * Spawns the dummy item on top of the shop. + */ + @Override + public void spawn() { + if (shop.getLocation().getWorld() == null) { + return; + } + final Location dispLoc = this.getDisplayLocation(); + try { + this.item = shop.getLocation().getWorld().dropItem(dispLoc, this.iStack); + this.item.setVelocity(new Vector(0, 0.1, 0)); + NMS.safeGuard(this.item); + } catch (final Exception e) { + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5148f69..f263692 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -8,6 +8,8 @@ preventhopper: false guititle: '&6[&b快捷商店&6]&r' #启用魔改库支持 usemagiclib: true +#启用魔改库支持 +fakeitem: true #税收数量 (decimal) - 例如 税收是0.05 玩家1 在玩家2的商店 购买了 50元的东西,那么,玩家1 减少 50, 玩家2 账户增加(1-0.05)*50, 并且 玩家2税收账户增加 (0.05)*50. tax: 0.00