diff --git a/README.md b/README.md index df979d5..656e895 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,30 @@ Developers can easily tap into the Mirage API by simply adding the Mirage depend ```xml - org.ipvp/groupId> - mirage/artifactId> - 2.0.0-SNAPSHOT + org.ipvp + mirage + 3.0.0-SNAPSHOT provided ``` +Make sure to register the packet adapters in your main class: + +```java +@Override +public void onEnable() { + ProtocolLibrary.getProtocolManager().addPacketListener(new BlockDigAdapter(this)); + ProtocolLibrary.getProtocolManager().addPacketListener(new BlockClickAdapter(this)); +} +``` + ### Sending Blocks ### Sending blocks is made simple through the usage of the ```BlockGenerator``` and ```FakeBlockSender``` interfaces. To begin, we will need to create a `BlockGenerator` and a `FakeBlockSender` instance for our own usage: ```java -private BlockGenerator generator = new SingleBlockGenerator(Material.WOOL, (byte) 14); +private BlockGenerator generator = new SingleBlockGenerator(Material.PURPLE_STAINED_GLASS); private FakeBlockSender fakeBlockSender = new PlayerBlockSender(player); ``` In this example, we will be sending fake blocks of red wool to players. diff --git a/pom.xml b/pom.xml index 2a23b5e..f7bb230 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.ipvp mirage - 2.2.0-SNAPSHOT + 3.0.0-SNAPSHOT Mirage An API used to send fake blocks to players @@ -53,28 +53,32 @@ - spigot-repo - https://hub.spigotmc.org/nexus/content/groups/public/ + paper-repo + https://papermc.io/repo/repository/maven-public/ + + + dmulloy2-repo + http://repo.dmulloy2.net/content/groups/public/ - org.spigotmc - spigot-api - 1.8-R0.1-SNAPSHOT + com.destroystokyo.paper + paper-api + 1.13.1-R0.1-SNAPSHOT provided com.comphenix.protocol - ProtocolLib - 3.6.5-SNAPSHOT + ProtocolLib-API + 4.4.0 provided com.comphenix.packetwrapper PacketWrapper - 1.7.10-R0.1-SNAPSHOT + 1.13-R0.1-SNAPSHOT compile diff --git a/src/main/java/org/ipvp/mirage/PlayerBlockSender.java b/src/main/java/org/ipvp/mirage/PlayerBlockSender.java index 79bbbaf..78518fb 100644 --- a/src/main/java/org/ipvp/mirage/PlayerBlockSender.java +++ b/src/main/java/org/ipvp/mirage/PlayerBlockSender.java @@ -1,36 +1,31 @@ package org.ipvp.mirage; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; - +import com.comphenix.packetwrapper.WrapperPlayServerMultiBlockChange; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.ChunkCoordIntPair; +import com.comphenix.protocol.wrappers.MultiBlockChangeInfo; +import com.comphenix.protocol.wrappers.WrappedBlockData; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; import org.ipvp.mirage.block.FakeBlock; import org.ipvp.mirage.block.FakeBlockImpl; import org.ipvp.mirage.block.SimpleBlockData; import org.ipvp.mirage.generator.BlockGenerator; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.ChunkCoordIntPair; -import com.comphenix.protocol.wrappers.MultiBlockChangeInfo; -import com.comphenix.protocol.wrappers.WrappedBlockData; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.SetMultimap; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; public class PlayerBlockSender implements FakeBlockSender { @@ -40,11 +35,13 @@ public class PlayerBlockSender implements FakeBlockSender { /** * Initializes a fake block sender for a specific player * - * @param player Player to recieve fake blocks + * @param player Player to receive fake blocks */ - public PlayerBlockSender(Player player) { + public PlayerBlockSender(Plugin plugin, Player player) { Objects.requireNonNull(player, "Player cannot be null"); this.who = player; + + player.setMetadata(BLOCK_SENDER_META, new FixedMetadataValue(plugin, this)); } @Override @@ -88,7 +85,7 @@ public void clearBlockAt(Vector location) { if (block != null) { Location loc = new Location(who.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); Block current = loc.getBlock(); - who.sendBlockChange(loc, current.getType(), current.getData()); + who.sendBlockChange(loc, current.getBlockData()); } } @@ -103,7 +100,7 @@ public void clearBlocks(Predicate test) { sentBlocks.values().stream() .filter(test) // Filter unwanted .forEach(fakeBlock -> { - Vector fakeLocation = fakeBlock.getLocation(); + Vector fakeLocation = fakeBlock.getVector(); if (fakeBlock.getWorld().isChunkLoaded(fakeLocation.getBlockX() >> 4, fakeLocation.getBlockZ() >> 4)) { FakeBlock.Data data = getCurrentData(fakeBlock); FakeBlock current = new FakeBlockImpl(data, fakeBlock.getWorld(), fakeLocation, fakeBlock.getGenerator()); @@ -116,9 +113,9 @@ public void clearBlocks(Predicate test) { // Gets an updated block data for a fake block private FakeBlock.Data getCurrentData(FakeBlock block) { - Vector loc = block.getLocation(); + Vector loc = block.getVector(); Block current = block.getWorld().getBlockAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - return new SimpleBlockData(current.getType(), current.getData()); + return new SimpleBlockData(current.getBlockData()); } // Handle block changes for a player @@ -134,7 +131,7 @@ private void handleBlockChanges(Collection input) { SetMultimap pairs = HashMultimap.create(); input.forEach(block -> { - Vector location = block.getLocation(); + Vector location = block.getVector(); int chunkX = location.getBlockX() >> 4; int chunkZ = location.getBlockZ() >> 4; if (block.getWorld().isChunkLoaded(chunkX, chunkZ)) { @@ -154,49 +151,27 @@ private void handleBlockChanges(Collection input) { // Sends a single block change to a player private void sendSingleBlockChange(FakeBlock block) { - Vector location = block.getLocation(); - Location loc = new Location(who.getWorld(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); - who.sendBlockChange(loc, block.getData().getType(), block.getData().getData()); + Location loc = block.getLocation(); + who.sendBlockChange(loc, block.getData().getData()); } // Sends a bulk block change in a chunk to a player private void sendBulkBlockChange(Chunk chunk, Set blocksToSend) throws IOException { PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE); - ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(blocksToSend.size()); - DataOutputStream dataOutputStream = new DataOutputStream(byteOutputStream); + WrapperPlayServerMultiBlockChange wrapper = new WrapperPlayServerMultiBlockChange(packet); - short[] ashort = new short[blocksToSend.size()]; - int[] blocks = new int[blocksToSend.size()]; + MultiBlockChangeInfo[] records = new MultiBlockChangeInfo[blocksToSend.size()]; int i = 0; for (FakeBlock block : blocksToSend) { - Vector location = block.getLocation(); - int blockID = block.getData().getType().getId(); - int data = block.getData().getData(); - // data = SpigotDebreakifier.getCorrectedData(blockID, data); - - blocks[i] = ((blockID & 0xFFF) << 4 | data & 0xF); - ashort[i] = ((short) ((location.getBlockX() & 0xF) << 12 | (location.getBlockZ() & 0xF) << 8 | location.getBlockY())); - - dataOutputStream.writeShort(ashort[i]); - dataOutputStream.writeShort(blocks[i]); - i++; + records[i++] = new MultiBlockChangeInfo(block.getLocation(), WrappedBlockData.createData(block.getData().getData())); } - int expectedSize = blocksToSend.size() * 4; - byte[] bulk = byteOutputStream.toByteArray(); - if (bulk.length != expectedSize) { - throw new IOException("Expected length: '" + expectedSize + "' doesn't match the generated length: '" + bulk.length + "'"); - } + wrapper.setChunk(new ChunkCoordIntPair(chunk.getX(), chunk.getZ())); + wrapper.setRecords(records); - // Write the data to the packet - packet.getChunkCoordIntPairs().write(0, new ChunkCoordIntPair(chunk.getX(), chunk.getZ())); - packet.getByteArrays().write(0, bulk); - packet.getIntegers().write(0, blocksToSend.size()); - packet.getSpecificModifier(short[].class).write(0, ashort); - packet.getIntegerArrays().write(0, blocks); try { - ProtocolLibrary.getProtocolManager().sendServerPacket(getPlayer(), packet); + ProtocolLibrary.getProtocolManager().sendServerPacket(getPlayer(), wrapper.getHandle()); } catch (InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/ipvp/mirage/block/FakeBlock.java b/src/main/java/org/ipvp/mirage/block/FakeBlock.java index 2a72399..cba5749 100644 --- a/src/main/java/org/ipvp/mirage/block/FakeBlock.java +++ b/src/main/java/org/ipvp/mirage/block/FakeBlock.java @@ -1,7 +1,9 @@ package org.ipvp.mirage.block; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.data.BlockData; import org.bukkit.util.Vector; import org.ipvp.mirage.generator.BlockGenerator; @@ -26,7 +28,7 @@ interface Data { * * @return the data */ - byte getData(); + BlockData getData(); } /** @@ -43,12 +45,21 @@ interface Data { */ World getWorld(); + /** + * Returns the location of this block. + * + * @return the location, as a vector + */ + Vector getVector(); + /** * Returns the location of this block. * * @return the location */ - Vector getLocation(); + default Location getLocation() { + return getVector().toLocation(getWorld()); + } /** * Returns the block generator that was used to create this block. diff --git a/src/main/java/org/ipvp/mirage/block/FakeBlockImpl.java b/src/main/java/org/ipvp/mirage/block/FakeBlockImpl.java index 720191b..cdb7345 100644 --- a/src/main/java/org/ipvp/mirage/block/FakeBlockImpl.java +++ b/src/main/java/org/ipvp/mirage/block/FakeBlockImpl.java @@ -29,7 +29,7 @@ public World getWorld() { } @Override - public Vector getLocation() { + public Vector getVector() { return position; } diff --git a/src/main/java/org/ipvp/mirage/block/SimpleBlockData.java b/src/main/java/org/ipvp/mirage/block/SimpleBlockData.java index 4124cd6..58e11b0 100644 --- a/src/main/java/org/ipvp/mirage/block/SimpleBlockData.java +++ b/src/main/java/org/ipvp/mirage/block/SimpleBlockData.java @@ -2,29 +2,28 @@ import com.google.common.base.Preconditions; import org.bukkit.Material; +import org.bukkit.block.data.BlockData; public class SimpleBlockData implements FakeBlock.Data { - private final Material type; - private final byte data; + private final BlockData data; public SimpleBlockData(Material type) { - this(type, (byte) 0); + this(type.createBlockData()); } - public SimpleBlockData(Material type, byte data) { - Preconditions.checkNotNull(type, "Type cannot be null"); - this.type = type; + public SimpleBlockData(BlockData data) { + Preconditions.checkNotNull(data, "Data cannot be null"); this.data = data; } @Override public Material getType() { - return type; + return data.getMaterial(); } @Override - public byte getData() { + public BlockData getData() { return data; } } diff --git a/src/main/java/org/ipvp/mirage/generator/SingleBlockGenerator.java b/src/main/java/org/ipvp/mirage/generator/SingleBlockGenerator.java index e677c67..3308bae 100644 --- a/src/main/java/org/ipvp/mirage/generator/SingleBlockGenerator.java +++ b/src/main/java/org/ipvp/mirage/generator/SingleBlockGenerator.java @@ -1,6 +1,7 @@ package org.ipvp.mirage.generator; import org.bukkit.Material; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import org.ipvp.mirage.block.FakeBlock.Data; @@ -10,8 +11,12 @@ public class SingleBlockGenerator implements BlockGenerator { private final Data data; - public SingleBlockGenerator(Material type, byte data) { - this(new SimpleBlockData(type, data)); + public SingleBlockGenerator(Material type) { + this(new SimpleBlockData(type)); + } + + public SingleBlockGenerator(BlockData data) { + this(new SimpleBlockData(data)); } public SingleBlockGenerator(Data data) { diff --git a/src/main/java/org/ipvp/mirage/protocol/BlockClickAdapter.java b/src/main/java/org/ipvp/mirage/protocol/BlockClickAdapter.java new file mode 100644 index 0000000..5899c0f --- /dev/null +++ b/src/main/java/org/ipvp/mirage/protocol/BlockClickAdapter.java @@ -0,0 +1,64 @@ +package org.ipvp.mirage.protocol; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.EnumWrappers; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.ipvp.mirage.FakeBlockSender; +import org.ipvp.mirage.block.FakeBlock; + +import java.util.Optional; + +public class BlockClickAdapter extends PacketAdapter { + + private final BlockFace[] FACES_TO_CHECK = new BlockFace[] {BlockFace.SELF, BlockFace.UP, BlockFace.DOWN, BlockFace.EAST, + BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH}; + + public BlockClickAdapter(JavaPlugin plugin) { + super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.USE_ITEM); + } + + @Override + public void onPacketReceiving(PacketEvent event) { + StructureModifier positions = event.getPacket().getBlockPositionModifier(); + StructureModifier directions = event.getPacket().getDirections(); + + + Player player = event.getPlayer(); + BlockPosition position = positions.read(0); + int x = position.getX(), y = position.getY(), z = position.getZ(); + Location location = new Location(player.getWorld(), x, y, z); + + Optional sender = FakeBlockSender.getFrom(player); + if (!sender.isPresent()) { + return; + } + + for (BlockFace face : FACES_TO_CHECK) { + Block block = location.getBlock().getRelative(face); + FakeBlock visualBlock = sender.get().getBlockAt(block.getLocation().toVector()); + if (visualBlock != null) { + event.setCancelled(true); + player.sendBlockChange(location, visualBlock.getData().getData()); + player.updateInventory(); + } + } + + if (event.isCancelled()) { + EnumWrappers.Direction direction = directions.read(0); + BlockFace bukkit = BlockFace.valueOf(direction.name()); + Block block = location.getBlock().getRelative(bukkit); + + player.sendBlockChange(block.getLocation(), Material.AIR.createBlockData()); + } + } +} diff --git a/src/main/java/org/ipvp/mirage/protocol/BlockDigAdapter.java b/src/main/java/org/ipvp/mirage/protocol/BlockDigAdapter.java index 6a1deb5..11ab41b 100644 --- a/src/main/java/org/ipvp/mirage/protocol/BlockDigAdapter.java +++ b/src/main/java/org/ipvp/mirage/protocol/BlockDigAdapter.java @@ -2,6 +2,9 @@ import java.util.Optional; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.ChunkPosition; +import com.comphenix.protocol.wrappers.EnumWrappers; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -18,22 +21,21 @@ public class BlockDigAdapter extends PacketAdapter { - private static final int STARTED_DIGGING = 0; - private static final int FINISHED_DIGGING = 2; - public BlockDigAdapter(JavaPlugin plugin) { super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.BLOCK_DIG); } @Override public void onPacketReceiving(PacketEvent event) { - StructureModifier modifier = event.getPacket().getIntegers(); + StructureModifier digTypes = event.getPacket().getPlayerDigTypes(); + StructureModifier positions = event.getPacket().getBlockPositionModifier(); try { - int status = modifier.read(4); - if (status == STARTED_DIGGING || status == FINISHED_DIGGING) { + EnumWrappers.PlayerDigType status = digTypes.read(0); + if (status == EnumWrappers.PlayerDigType.START_DESTROY_BLOCK || status == EnumWrappers.PlayerDigType.STOP_DESTROY_BLOCK) { Player player = event.getPlayer(); - int x = modifier.read(0), y = modifier.read(1), z = modifier.read(2); + BlockPosition position = positions.read(0); + int x = position.getX(), y = position.getY(), z = position.getZ(); Location location = new Location(player.getWorld(), x, y, z); Optional sender = FakeBlockSender.getFrom(player); @@ -45,8 +47,8 @@ public void onPacketReceiving(PacketEvent event) { if (visualBlock != null) { event.setCancelled(true); FakeBlock.Data data = visualBlock.getData(); - if (status == FINISHED_DIGGING || player.getGameMode() == GameMode.CREATIVE) { - player.sendBlockChange(location, data.getType(), data.getData()); + if (status == EnumWrappers.PlayerDigType.STOP_DESTROY_BLOCK || player.getGameMode() == GameMode.CREATIVE) { + player.sendBlockChange(location, data.getData()); } } } diff --git a/src/main/java/org/ipvp/mirage/protocol/BlockPlaceAdapter.java b/src/main/java/org/ipvp/mirage/protocol/BlockPlaceAdapter.java deleted file mode 100644 index c94170e..0000000 --- a/src/main/java/org/ipvp/mirage/protocol/BlockPlaceAdapter.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.ipvp.mirage.protocol; - -import java.util.Optional; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.ipvp.mirage.FakeBlockSender; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.ListenerPriority; -import com.comphenix.protocol.events.PacketAdapter; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.StructureModifier; -import org.ipvp.mirage.block.FakeBlock; - -public class BlockPlaceAdapter extends PacketAdapter { - - public BlockPlaceAdapter(JavaPlugin plugin) { - super(plugin, ListenerPriority.NORMAL, PacketType.Play.Client.BLOCK_PLACE); - } - - @Override - public void onPacketReceiving(PacketEvent event) { - StructureModifier modifier = event.getPacket().getIntegers(); - Player player = event.getPlayer(); - - try { - int face = modifier.read(3); - if (face == 255) { - return; - } - - int x = modifier.read(0); - int y = modifier.read(1); - int z = modifier.read(2); - Location clickedBlock = new Location(player.getWorld(), x, y, z); - clickedBlock = clickedBlock.getBlock().getLocation(); - - Optional opt = FakeBlockSender.getFrom(player); - if (!opt.isPresent()) { - return; - } - - FakeBlockSender sender = opt.get(); - FakeBlock block = sender.getBlockAt(clickedBlock.toVector()); - - if (block == null) { - return; - } - - Location placedLocation = clickedBlock.clone(); - switch (face) { - case 2: - placedLocation.add(0, 0, -1); - break; - case 3: - placedLocation.add(0, 0, 1); - break; - case 4: - placedLocation.add(-1, 0, 0); - break; - case 5: - placedLocation.add(1, 0, 0); - break; - default: - return; - } - - event.setCancelled(true); - - // Revert the block placing for the player - if (sender.getBlockAt(placedLocation.toVector()) == null) { - player.sendBlockChange(placedLocation, Material.AIR, (byte) 0); - player.updateInventory(); - } - } catch (FieldAccessException ex) { - ex.printStackTrace(); - } - } -}