From fb7873779dc4d485a292eed85986ad5bf0956af1 Mon Sep 17 00:00:00 2001 From: Justin <33465177+BitByLogics@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:05:44 -0500 Subject: [PATCH 1/3] Begin implementation of PacketBlockGroup/move to new project architecture --- .../adapter/BlockBreakAdapter.java | 2 +- .../packetblocks/block/PacketBlock.java | 437 ++---------------- .../packetblocks/block/PacketBlockHolder.java | 10 + .../block/PacketBlockManager.java | 8 +- .../block/PacketBlockPlayerData.java | 78 +--- .../packetblocks/data/DataHandler.java | 177 +++++++ .../packetblocks/data/DataHolder.java | 117 +++++ .../packetblocks/group/PacketBlockGroup.java | 94 ++++ .../metadata/MetadataHandler.java | 81 ++++ .../packetblocks/metadata/MetadataHolder.java | 75 +++ .../viewer/PacketBlockViewer.java | 32 ++ .../packetblocks/viewer/ViewerHandler.java | 209 +++++++++ .../packetblocks/viewer/ViewerHolder.java | 170 +++++++ .../viewer/impl/GroupPacketBlockViewer.java | 32 ++ .../viewer/impl/SinglePacketBlockViewer.java | 14 + 15 files changed, 1056 insertions(+), 480 deletions(-) create mode 100644 src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHandler.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHolder.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/viewer/PacketBlockViewer.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHandler.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHolder.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/viewer/impl/GroupPacketBlockViewer.java create mode 100644 src/main/java/net/bitbylogic/packetblocks/viewer/impl/SinglePacketBlockViewer.java diff --git a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java index d5049ca..e5b0e7a 100644 --- a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java +++ b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java @@ -41,7 +41,7 @@ public BlockBreakAdapter(@NonNull PacketBlockManager manager) { public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() != PacketType.Play.Client.PLAYER_DIGGING) return; - Player player = (Player) event.getPlayer(); + Player player = event.getPlayer(); WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event); Vector3i position = packet.getBlockPosition(); diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java index 58b9996..caa4c5c 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java @@ -2,10 +2,13 @@ import lombok.Getter; import lombok.NonNull; -import lombok.Setter; import net.bitbylogic.packetblocks.PacketBlocks; +import net.bitbylogic.packetblocks.data.DataHandler; import net.bitbylogic.packetblocks.event.PacketBlockBreakEvent; +import net.bitbylogic.packetblocks.metadata.MetadataHandler; import net.bitbylogic.packetblocks.util.BoundingBoxes; +import net.bitbylogic.packetblocks.viewer.ViewerHandler; +import net.bitbylogic.packetblocks.viewer.impl.SinglePacketBlockViewer; import net.bitbylogic.utils.location.ChunkPosition; import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Bukkit; @@ -17,12 +20,10 @@ import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.Predicate; -import java.util.function.Supplier; +import java.util.List; @Getter -public class PacketBlock { +public class PacketBlock implements PacketBlockHolder { private final WorldPosition position; private final ChunkPosition chunk; @@ -31,242 +32,49 @@ public class PacketBlock { private final List boundingBoxes; - private final Set> viewConditions; - private final HashMap viewers; - private final HashMap metadata; - - private BlockData blockData; - - @Setter - private int breakSpeed = -1; - - @Setter - private boolean addViewerOnJoin; - - @Setter - private boolean globalBreakAnimation; + private final DataHandler dataHandler; + private final ViewerHandler viewerHandler; + private final MetadataHandler metadataHandler; /** * Constructs a new PacketBlock instance associated with a specific location and block data. * The PacketBlock represents a virtual block that interacts with players based on certain * conditions, metadata, and viewer states. * - * @param location The location of the block in the world. Must not be null. + * @param location The location of the block in the world. Must not be null. * @param blockData The visual and physical characteristics of the block. Must not be null. */ protected PacketBlock(@NonNull Location location, @NonNull BlockData blockData) { - this.position = WorldPosition.ofBlock(location); - this.chunk = position.toChunkPosition(); - - this.location = location; - - this.boundingBoxes = BoundingBoxes.getBoxes(blockData); - this.blockData = blockData; - - this.viewConditions = new HashSet<>(); - this.viewers = new HashMap<>(); - this.metadata = new HashMap<>(); - } - - /** - * Checks if the specified player meets all the conditions required to view this Packet Block. - * The conditions are evaluated using the stream of view requirements associated with this block. - * - * @param player The player whose eligibility to view the block is being checked. Must not be null. - * @return True if the player satisfies all the view conditions, otherwise false. - */ - public boolean canView(@NonNull Player player) { - return viewConditions.stream().allMatch(viewRequirement -> viewRequirement.test(player)); - } - - /** - * Checks whether the specified player is a viewer of this Packet Block. - * - * @param player The player to check. Must not be null. - * @return True if the player is a viewer of this Packet Block, false otherwise. - */ - public boolean isViewer(@NonNull Player player) { - return viewers.containsKey(player.getUniqueId()); - } - - /** - * Retrieves the {@link PacketBlockPlayerData} associated with the given player if they are a viewer - * of this Packet Block. - * - * @param player The player whose viewer data is to be retrieved. Must not be null. - * @return An {@link Optional} containing the {@link PacketBlockPlayerData} associated with the player - * if they are a viewer, or an empty optional if they are not a viewer. - */ - public Optional getViewer(@NonNull Player player) { - return Optional.ofNullable(viewers.get(player.getUniqueId())); - } - - /** - * Attempts to add the specified player as a viewer to this Packet Block if they meet the viewing conditions. - * If the player is successfully added, an optional containing the associated {@link PacketBlockPlayerData} - * instance is returned. If the player is already a viewer, their existing {@link PacketBlockPlayerData} is returned. - * No viewer is added if the player does not meet the viewing conditions. - * - * @param player The player to attempt to add as a viewer. - * @param sendUpdate Whether to send a block update to the player upon successfully adding them as a viewer. - * @return An {@link Optional} containing the {@link PacketBlockPlayerData} associated with the player - * if they meet the conditions and are added as a viewer, or an empty optional otherwise. - */ - public Optional attemptAddViewer(@NonNull Player player, boolean sendUpdate) { - if (!canView(player)) { - return Optional.empty(); - } - - if(isViewer(player)) { - return getViewer(player); - } - - PacketBlockPlayerData playerData = new PacketBlockPlayerData(null, () -> blockData, breakSpeed); - viewers.put(player.getUniqueId(), playerData); - - if(sendUpdate) { - sendUpdate(player); - } - - return Optional.of(playerData); - } - - /** - * Adds the specified player as a viewer to this Packet Block. - * This method bypasses any view requirements that are set. - * - * @param player The player to add as a viewer. - * @return The {@link PacketBlockPlayerData} instance associated with the added player. - */ - public PacketBlockPlayerData addViewer(@NonNull Player player) { - PacketBlockPlayerData playerData = new PacketBlockPlayerData(null, () -> blockData, breakSpeed); - viewers.put(player.getUniqueId(), playerData); - return playerData; - } - - /** - * Adds the specified player as a viewer to this Packet Block if they are not already added - * and updates their block state to reflect the current state of the Packet Block. - * This method bypasses any view requirements that are set. - * - * @param player The player to add as a viewer and send block updates to - * @return The {@link PacketBlockPlayerData} instance associated with the added player - */ - public PacketBlockPlayerData addAndUpdateViewer(@NonNull Player player) { - PacketBlockPlayerData playerData = addViewer(player); - sendUpdate(player); - return playerData; - } - - /** - * Adds a viewer to this Packet Block with a custom BlockData supplier. - * This method bypasses any view requirements that are set. - * - * @param player The player to add as a viewer. - * @param blockDataSupplier A supplier that provides the BlockData for this player. - * @return The {@link PacketBlockPlayerData} instance associated with the added player. - */ - public PacketBlockPlayerData addViewer(@NonNull Player player, @NonNull Supplier blockDataSupplier) { - PacketBlockPlayerData playerData = new PacketBlockPlayerData(null, blockDataSupplier, breakSpeed); - viewers.put(player.getUniqueId(), playerData); - - return playerData; - } - - /** - * Adds a viewer for the block data and sends an update to the specified player. - * - * @param player the player to be added as a viewer, must not be null - * @param blockDataSupplier a supplier for providing the block data, must not be null - * @return the PacketBlockPlayerData object associated with the player - */ - public PacketBlockPlayerData addAndUpdateViewer(@NonNull Player player, @NonNull Supplier blockDataSupplier) { - PacketBlockPlayerData playerData = addViewer(player, blockDataSupplier); - sendUpdate(player); - - return playerData; - } - - /** - * Removes the specified player from the list of viewers if they are present. - * If the player is successfully removed, it sends a block change notification - * to the player for a specific location. - * - * @param player the player to be removed from the viewers list; must not be null - */ - public void removeViewer(@NonNull Player player) { - if(!viewers.containsKey(player.getUniqueId())) { - return; - } - - viewers.remove(player.getUniqueId()); - player.sendBlockChange(location, location.getBlock().getBlockData()); + this(location, blockData, -1); } /** - * Sets the block data for all viewers and updates them accordingly. - * - * @param blockData the block data to be set for all viewers - */ - public void setBlockDataForAll(@NonNull BlockData blockData) { - this.blockData = blockData; - viewers.values().forEach(playerData -> playerData.setBlockData(blockData)); - sendUpdates(); - } - - /** - * Sets the block data supplier for all viewers and updates the block data - * for each viewer based on the provided BlockData. - * - * @param blockData the BlockData object to be supplied to all viewers - */ - public void setBlockDataSupplierForAll(@NonNull BlockData blockData) { - this.blockData = blockData; - viewers.values().forEach(playerData -> playerData.setBlockDataSupplier(() -> blockData)); - sendUpdates(); - } - - /** - * Sets the block data for a specific player. + * Constructs a new PacketBlock instance associated with a specific location and block data. + * The PacketBlock represents a virtual block that interacts with players based on certain + * conditions, metadata, and viewer states. * - * @param player the player for whom the block data is being set; must not be null - * @param blockData the block data to associate with the player; can be null to reset or remove the block data + * @param location The location of the block in the world. Must not be null. + * @param blockData The visual and physical characteristics of the block. Must not be null. + * @param breakSpeed The speed at which the block breaks, in ticks. */ - public void setBlockData(@NonNull Player player, @Nullable BlockData blockData) { - Optional optionalPlayerData = getViewer(player); - - if (optionalPlayerData.isEmpty()) { - return; - } + protected PacketBlock(@NonNull Location location, @NonNull BlockData blockData, int breakSpeed) { + this.position = WorldPosition.ofBlock(location); + this.chunk = position.toChunkPosition(); - optionalPlayerData.get().setBlockData(blockData); - } + this.location = location; - /** - * Sets the BlockData supplier for a specific player, allowing for dynamic control over block data. - * - * @param player the player for whom the BlockData supplier is being set, must not be null - * @param blockData the BlockData object to be supplied, must not be null - */ - public void setBlockDataSupplier(@NonNull Player player, @NonNull BlockData blockData) { - Optional optionalPlayerData = getViewer(player); + this.boundingBoxes = BoundingBoxes.getBoxes(blockData); - if (optionalPlayerData.isEmpty()) { - return; - } + this.viewerHandler = new ViewerHandler<>( + player -> blockData, + this::sendUpdate, + player -> player.sendBlockChange(location, location.getBlock().getBlockData()), + () -> new SinglePacketBlockViewer(blockData, () -> blockData, breakSpeed) + ); - optionalPlayerData.get().setBlockDataSupplier(() -> blockData); - } + this.dataHandler = new DataHandler<>(this, this::sendUpdate, blockData, breakSpeed); - /** - * Sets the block data for the specified player and sends an update. - * - * @param player the player for whom the block data is being set, must not be null - * @param blockData the block data to be set, can be null - */ - public void setBlockDataAndUpdate(@NonNull Player player, @Nullable BlockData blockData) { - setBlockData(player, blockData); - sendUpdate(player); + this.metadataHandler = new MetadataHandler(); } /** @@ -281,138 +89,8 @@ public void setBlockDataAndUpdate(@NonNull Player player, @Nullable BlockData bl */ public BlockState getBlockState(@NonNull Player player) { return getViewer(player) - .map(playerData -> playerData.getBlockData() == null ? playerData.getBlockDataSupplier().get() : playerData.getBlockData()) - .orElse(blockData).createBlockState().copy(location); - } - - /** - * Adds metadata to the PacketBlock. The metadata is a key-value pair where the key is a unique identifier, - * and the value is associated data. If the specified key already exists in the metadata, the method will - * do nothing. - * - * @param key The unique key for the metadata. Must not be null. - * @param object The value associated with the metadata key. Must not be null. - */ - public void addMetadata(@NonNull String key, @NonNull Object object) { - if (metadata.containsKey(key)) { - return; - } - - metadata.put(key, object); - } - - /** - * Removes the metadata associated with the specified key. If the key exists in the metadata map, - * it will be removed. - * - * @param key the key of the metadata to be removed; must not be null - */ - public void removeMetadata(@NonNull String key) { - metadata.remove(key); - } - - /** - * Checks if the metadata contains the specified key. - * - * @param key the key to check for in the metadata; must not be null - * @return true if the metadata contains the specified key, false otherwise - */ - public boolean hasMetadata(@NonNull String key) { - return metadata.containsKey(key); - } - - /** - * Retrieves the metadata value associated with the specified key. - * If the key does not exist, a default fallback value is used. - * - * @param key The key for which the metadata value is to be retrieved. Must not be null. - * @return The metadata value associated with the given key, or null if no value exists and no fallback is provided. - */ - public Object getMetadata(@NonNull String key) { - return getMetadata(key, null); - } - - /** - * Retrieves metadata associated with the specified key and casts it to the desired type. - * If the key is not present or the cast fails, exceptions may be thrown. - * - * @param key The key used to reference the metadata. Must not be null. - * @param clazz The class type to which the metadata should be cast. Must not be null. - * @param The type to which the metadata will be cast. - * @return The metadata object corresponding to the specified key, cast to the specified type. - * Returns null if the key is not present or the metadata value itself is null. - */ - public T getMetadataAs(@NonNull String key, @NonNull Class clazz) { - return (T) getMetadata(key, null); - } - - /** - * Retrieves the metadata value associated with the given key. - * If the key is not present in the metadata, the fallback value is returned. - * - * @param key The key to look up in the metadata. Must not be null. - * @param fallback The fallback value to return if the key is not present in the metadata. Can be null. - * @return The metadata value associated with the key, or the provided fallback value if the key is not present. - */ - public Object getMetadata(@NonNull String key, @Nullable Object fallback) { - return metadata.getOrDefault(key, fallback); - } - - /** - * Adds a metadata key-value pair to the specific player's {@link PacketBlockPlayerData}, if the player is a viewer. - * - * @param player The player whose metadata is being updated. Must not be null. - * @param key The key for the metadata entry. Must not be null. - * @param object The value associated with the specified key. Must not be null. - */ - public void addMetadata(@NonNull Player player, @NonNull String key, @NonNull Object object) { - getViewer(player).ifPresent(playerData -> playerData.addMetadata(key, object)); - } - - /** - * Removes the metadata associated with a specific key for the given player, if the player is a viewer - * of this PacketBlock. This allows targeted removal of metadata entries tied to individual players. - * - * @param player The player whose associated metadata is to be removed. Must not be null. - * @param key The key of the metadata to be removed. Must not be null. - */ - public void removeMetadata(@NonNull Player player, @NonNull String key) { - getViewer(player).ifPresent(playerData -> playerData.removeMetadata(key)); - } - - /** - * Checks if the specified player has metadata associated with the given key within this Packet Block. - * - * @param player The player whose metadata association is to be checked. Must not be null. - * @param key The key of the metadata to check. Must not be null. - * @return True if the specified player has metadata associated with the given key, otherwise false. - */ - public boolean hasMetadata(@NonNull Player player, @NonNull String key) { - return getViewer(player).map(playerData -> playerData.hasMetadata(key)).orElse(false); - } - - /** - * Retrieves the metadata associated with a specific player and a given key. - * - * @param player The player for whom metadata is being retrieved. Must not be null. - * @param key The key identifying the metadata to be retrieved. Must not be null. - * @return The metadata object associated with the specified player and key, or null if no metadata exists for the key. - */ - public Object getMetadata(@NonNull Player player, @NonNull String key) { - return getMetadataAs(player, key, null); - } - - /** - * Retrieves metadata associated with a given key for a specified player. If no metadata is found, - * a fallback value will be returned. - * - * @param player the player whose metadata is to be retrieved; must not be null - * @param key the key associated with the metadata to retrieve; must not be null - * @param fallback the value to return if no associated metadata is found; can be null - * @return the metadata value associated with the specified key, or the fallback value if no metadata is found - */ - public Object getMetadataAs(@NonNull Player player, @NonNull String key, @Nullable Object fallback) { - return getViewer(player).map(playerData -> playerData.getMetadata(key)).orElse(fallback); + .map(playerData -> playerData.getData() == null ? playerData.getDataSupplier().get() : playerData.getData()) + .orElse(getData()).createBlockState().copy(location); } /** @@ -424,41 +102,6 @@ public void sendUpdate(@NonNull Player player) { player.sendBlockChange(location, getBlockState(player).getBlockData()); } - /** - * Sends block updates to all the viewers currently tracking the block at the specified location. - * - * This method iterates through all viewers stored in the `viewers` map and attempts to send a - * block update to each. If a viewer no longer exists or is offline, they are removed from the - * `viewers` map. - * - * The block state sent to each viewer is dependent on the `getBlockState(Player)` implementation, - * which determines the block's appearance based on the specific viewer. - */ - public void sendUpdates() { - Iterator viewerIterator = viewers.keySet().iterator(); - - while (viewerIterator.hasNext()) { - Player viewer = Bukkit.getPlayer(viewerIterator.next()); - - if (viewer == null) { - viewerIterator.remove(); - continue; - } - - viewer.sendBlockChange(location, getBlockState(viewer).getBlockData()); - } - } - - /** - * Calculates and retrieves the break speed for the given player. - * - * @param player the player whose break speed is being requested, must not be null - * @return the calculated break speed for the player; returns a default value if not available - */ - public int getBreakSpeed(@NonNull Player player) { - return getViewer(player).map(PacketBlockPlayerData::getBreakSpeed).orElse(breakSpeed); - } - /** * Simulates the breaking action for the specified player. * This method triggers a simulation of a break event for the provided player. @@ -474,7 +117,7 @@ public void simulateBreak(@NonNull Player player) { * Simulates the block-breaking process, triggering a custom event and handling item drops based on the event state. * * @param player The player performing the block-breaking action. Must not be null. - * @param tool The tool used by the player to break the block. Can be null if no tool is used. + * @param tool The tool used by the player to break the block. Can be null if no tool is used. */ public void simulateBreak(@NonNull Player player, @Nullable ItemStack tool) { Bukkit.getScheduler().runTask(PacketBlocks.getInstance(), () -> { @@ -496,18 +139,4 @@ public void simulateBreak(@NonNull Player player, @Nullable ItemStack tool) { }); } - /** - * Adds a viewing condition to the list of conditions if it is not already present. - * - * @param condition the condition to be added, represented as a {@code Predicate}. - * This condition evaluates to determine whether a player meets the viewing criteria. - */ - public void addViewCondition(@NonNull Predicate condition) { - if(viewConditions.contains(condition)) { - return; - } - - viewConditions.add(condition); - } - } diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java new file mode 100644 index 0000000..5768210 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java @@ -0,0 +1,10 @@ +package net.bitbylogic.packetblocks.block; + +import net.bitbylogic.packetblocks.data.DataHolder; +import net.bitbylogic.packetblocks.metadata.MetadataHolder; +import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; +import net.bitbylogic.packetblocks.viewer.ViewerHolder; + +public interface PacketBlockHolder> extends DataHolder, ViewerHolder, MetadataHolder { + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java index 841b6e9..ec74633 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java @@ -33,7 +33,7 @@ public class PacketBlockManager { * Creates a new {@link PacketBlock} instance at the specified location with the given block data. * The created block is registered within the internally managed collection, ensuring it is * appropriately tracked for further operations. - * + *

* If the block already exists in the specified chunk, no duplicate will be created, * and the existing instance will be returned. * @@ -241,7 +241,7 @@ public List getBlocksByViewerWithMeta(@NonNull Player player, @NonN blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { - if (!block.getViewers().containsKey(player.getUniqueId()) || !block.getMetadata().containsKey(metaKey)) { + if (!block.getViewers().containsKey(player.getUniqueId()) || !block.hasMetadata(metaKey)) { return; } @@ -263,7 +263,7 @@ public List getBlocksByMetadata(@NonNull String key) { blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { - if (!block.getMetadata().containsKey(key)) { + if (!block.hasMetadata(key)) { return; } @@ -379,7 +379,7 @@ public List getHitBlocksByViewerWithMeta(@NonNull Player player, @N return; } - if (!block.getMetadata().containsKey(metaKey)) { + if (!block.hasMetadata(metaKey)) { return; } diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockPlayerData.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockPlayerData.java index 0733a22..ba2f115 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockPlayerData.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockPlayerData.java @@ -4,90 +4,26 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import net.bitbylogic.packetblocks.metadata.MetadataHandler; +import net.bitbylogic.packetblocks.metadata.MetadataHolder; import org.bukkit.block.data.BlockData; -import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.function.Supplier; @Getter @Setter @AllArgsConstructor -public class PacketBlockPlayerData { +public class PacketBlockPlayerData implements MetadataHolder { - private final HashMap metadata = new HashMap<>(); + private final MetadataHandler metadataHandler = new MetadataHandler(); private BlockData blockData; private Supplier blockDataSupplier; private int breakSpeed; - /** - * Adds a metadata entry with the specified key and object. - * If the key already exists in the metadata map, the method does nothing. - * - * @param key the key used to identify the metadata entry; must not be null - * @param object the object to be associated with the key; must not be null - */ - public void addMetadata(@NonNull String key, @NonNull Object object) { - if (metadata.containsKey(key)) { - return; - } - - metadata.put(key, object); - } - - /** - * Removes metadata associated with the specified key from the metadata map. - * - * @param key the key whose associated metadata is to be removed. Must not be null. - */ - public void removeMetadata(@NonNull String key) { - metadata.remove(key); - } - - /** - * Checks if a metadata entry with the specified key exists. - * - * @param key the key to check for metadata presence, must not be null - * @return true if metadata with the given key exists, false otherwise - */ - public boolean hasMetadata(@NonNull String key) { - return metadata.containsKey(key); - } - - /** - * Retrieves the metadata associated with the specified key. - * - * @param key the key to look up in the metadata map; must not be null. - * @return the metadata value associated with the given key, or null if no value is present for the key. - */ - public Object getMetadata(@NonNull String key) { - return getMetadata(key, null); - } - - /** - * Retrieves the metadata associated with the given key and casts it to the specified type. - * - * @param key the key for the metadata entry, must not be null - * @param clazz the class type to cast the metadata value to, must not be null - * @return the metadata value associated with the provided key, cast to the desired type, - * or null if the key does not exist or the value could not be cast - * @throws ClassCastException if the metadata value cannot be cast to the specified type - */ - public T getMetadataAs(@NonNull String key, @NonNull Class clazz) { - return (T) getMetadata(key, null); - } - - /** - * Retrieves the metadata associated with the given key. If no metadata exists for the key, - * returns the specified fallback value. - * - * @param key the key whose associated metadata is to be returned. Must not be null. - * @param fallback the value to return if no metadata is found for the given key. May be null. - * @return the metadata associated with the specified key, or the fallback value if no metadata is found. - */ - public Object getMetadata(@NonNull String key, @Nullable Object fallback) { - return metadata.getOrDefault(key, fallback); + @Override + public @NonNull MetadataHandler getMetadataHandler() { + return metadataHandler; } } diff --git a/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java b/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java new file mode 100644 index 0000000..ccf2535 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java @@ -0,0 +1,177 @@ +package net.bitbylogic.packetblocks.data; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; +import net.bitbylogic.packetblocks.viewer.ViewerHolder; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; + +public class DataHandler> { + + @Getter(AccessLevel.NONE) + private final ViewerHolder viewerHandler; + private final Consumer updateConsumer; + + public T data; + + private int breakSpeed = -1; + + private boolean addViewerOnJoin; + private boolean globalBreakAnimation; + + public DataHandler(ViewerHolder viewerHandler, Consumer updateConsumer, T data, int breakSpeed) { + this.viewerHandler = viewerHandler; + this.updateConsumer = updateConsumer; + this.data = data; + this.breakSpeed = breakSpeed; + } + + /** + * Sets the data for all viewers and updates them accordingly. + * + * @param data the block data to be set for all viewers + */ + protected void setDataForAll(@NonNull T data) { + for (V viewer : viewerHandler.getViewers().values()) { + viewer.setData(data); + } + + sendUpdates(); + } + + /** + * Sets the block data supplier for all viewers and updates the block data + * for each viewer based on the provided BlockData. + * + * @param data the BlockData object to be supplied to all viewers + */ + protected void setDataSupplierForAll(@NonNull T data) { + this.data = data; + + for (V viewer : viewerHandler.getViewers().values()) { + viewer.setDataSupplier(() -> data); + } + + sendUpdates(); + } + + /** + * Sets the data for a specific player. + * + * @param player the player for whom the block data is being set; must not be null + * @param data the data to associate with the player; can be null to reset or remove the block data + */ + protected void setData(@NonNull Player player, @Nullable T data) { + Optional optionalPlayerData = viewerHandler.getViewer(player); + + if (optionalPlayerData.isEmpty()) { + return; + } + + optionalPlayerData.get().setData(data); + } + + /** + * Sets the data supplier for a specific player, allowing for dynamic control over block data. + * + * @param player the player for whom the BlockData supplier is being set, must not be null + * @param data the data object to be supplied, must not be null + */ + protected void setDataSupplier(@NonNull Player player, @NonNull T data) { + Optional optionalPlayerData = viewerHandler.getViewer(player); + + if (optionalPlayerData.isEmpty()) { + return; + } + + optionalPlayerData.get().setDataSupplier(() -> data); + } + + /** + * Sets the block data for the specified player and sends an update. + * + * @param player the player for whom the block data is being set, must not be null + * @param data the block data to be set, can be null + */ + protected void setBlockDataAndUpdate(@NonNull Player player, @Nullable T data) { + setData(player, data); + updateConsumer.accept(player); + } + + /** + * Calculates and retrieves the break speed for the given player. + * + * @param player the player whose break speed is being requested, must not be null + * @return the calculated break speed for the player; returns a default value if not available + */ + protected int getBreakSpeed(@NonNull Player player) { + return viewerHandler.getViewer(player).map(PacketBlockViewer::getBreakSpeed).orElse(breakSpeed); + } + + /** + * Sends block updates to all the viewers currently tracking the block at the specified location. + *

+ * This method iterates through all viewers stored in the `viewers` map and attempts to send a + * block update to each. If a viewer no longer exists or is offline, they are removed from the + * `viewers` map. + *

+ * The block state sent to each viewer is dependent on the `getBlockState(Player)` implementation, + * which determines the block's appearance based on the specific viewer. + */ + protected void sendUpdates() { + Iterator viewerIterator = viewerHandler.getViewers().keySet().iterator(); + + while (viewerIterator.hasNext()) { + Player viewer = Bukkit.getPlayer(viewerIterator.next()); + + if (viewer == null) { + viewerIterator.remove(); + continue; + } + + updateConsumer.accept(viewer); + } + } + + protected T getData() { + return data; + } + + protected int getBreakSpeed() { + return breakSpeed; + } + + protected boolean isAddViewerOnJoin() { + return addViewerOnJoin; + } + + protected boolean isGlobalBreakAnimation() { + return globalBreakAnimation; + } + + protected void setBreakSpeed(int breakSpeed) { + this.breakSpeed = breakSpeed; + } + + protected void setAddViewerOnJoin(boolean addViewerOnJoin) { + this.addViewerOnJoin = addViewerOnJoin; + } + + protected void setGlobalBreakAnimation(boolean globalBreakAnimation) { + this.globalBreakAnimation = globalBreakAnimation; + } + + protected void setData(T data) { + this.data = data; + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java b/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java new file mode 100644 index 0000000..ceb2afa --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java @@ -0,0 +1,117 @@ +package net.bitbylogic.packetblocks.data; + +import lombok.NonNull; +import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +public interface DataHolder> { + + @NonNull DataHandler getDataHandler(); + + /** + * Sets the data for all viewers and updates them accordingly. + * + * @param data the block data to be set for all viewers + */ + default void setDataForAll(@NonNull T data) { + getDataHandler().setDataForAll(data); + } + + /** + * Sets the block data supplier for all viewers and updates the block data + * for each viewer based on the provided BlockData. + * + * @param data the BlockData object to be supplied to all viewers + */ + default void setDataSupplierForAll(@NonNull T data) { + getDataHandler().setDataSupplierForAll(data); + } + + /** + * Sets the data for a specific player. + * + * @param player the player for whom the block data is being set; must not be null + * @param data the data to associate with the player; can be null to reset or remove the block data + */ + default void setData(@NonNull Player player, @Nullable T data) { + getDataHandler().setData(player, data); + } + + /** + * Sets the data supplier for a specific player, allowing for dynamic control over block data. + * + * @param player the player for whom the BlockData supplier is being set, must not be null + * @param data the data object to be supplied, must not be null + */ + default void setDataSupplier(@NonNull Player player, @NonNull T data) { + getDataHandler().setDataSupplier(player, data); + } + + /** + * Sets the block data for the specified player and sends an update. + * + * @param player the player for whom the block data is being set, must not be null + * @param data the block data to be set, can be null + */ + default void setBlockDataAndUpdate(@NonNull Player player, @Nullable T data) { + getDataHandler().setBlockDataAndUpdate(player, data); + } + + /** + * Calculates and retrieves the break speed for the given player. + * + * @param player the player whose break speed is being requested, must not be null + * @return the calculated break speed for the player; returns a default value if not available + */ + default int getBreakSpeed(@NonNull Player player) { + return getDataHandler().getBreakSpeed(player); + } + + /** + * Sends block updates to all the viewers currently tracking the block at the specified location. + *

+ * This method iterates through all viewers stored in the `viewers` map and attempts to send a + * block update to each. If a viewer no longer exists or is offline, they are removed from the + * `viewers` map. + *

+ * The block state sent to each viewer is dependent on the `getBlockState(Player)` implementation, + * which determines the block's appearance based on the specific viewer. + */ + default void sendUpdates() { + getDataHandler().sendUpdates(); + } + + default T getData() { + return getDataHandler().getData(); + } + + default boolean isAddViewerOnJoin() { + return getDataHandler().isAddViewerOnJoin(); + } + + default boolean isGlobalBreakAnimation() { + return getDataHandler().isGlobalBreakAnimation(); + } + + default int getBreakSpeed() { + return getDataHandler().getBreakSpeed(); + } + + default void setBreakSpeed(int breakSpeed) { + getDataHandler().setBreakSpeed(breakSpeed); + } + + default void setAddViewerOnJoin(boolean addViewerOnJoin) { + getDataHandler().setAddViewerOnJoin(addViewerOnJoin); + } + + default void setGlobalBreakAnimation(boolean globalBreakAnimation) { + getDataHandler().setGlobalBreakAnimation(globalBreakAnimation); + } + + default void setData(T data) { + getDataHandler().setData(data); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java b/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java new file mode 100644 index 0000000..7c78378 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java @@ -0,0 +1,94 @@ +package net.bitbylogic.packetblocks.group; + +import lombok.Getter; +import lombok.NonNull; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; +import net.bitbylogic.packetblocks.data.DataHandler; +import net.bitbylogic.packetblocks.metadata.MetadataHandler; +import net.bitbylogic.packetblocks.viewer.ViewerHandler; +import net.bitbylogic.packetblocks.viewer.impl.GroupPacketBlockViewer; +import net.bitbylogic.utils.location.WorldPosition; +import org.bukkit.Location; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.util.BoundingBox; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Getter +public class PacketBlockGroup implements PacketBlockHolder, GroupPacketBlockViewer> { + + private final Map cachedLocations; + + private final List boundingBoxes; + + private final DataHandler, GroupPacketBlockViewer> dataHandler; + private final ViewerHandler, GroupPacketBlockViewer> viewerHandler; + private final MetadataHandler metadataHandler; + + public PacketBlockGroup(@NonNull Map blockLocations, int breakSpeed) { + int size = blockLocations.size(); + + this.cachedLocations = new HashMap<>(size); + this.boundingBoxes = new ArrayList<>(size); + + Map positions = new HashMap<>(size); + + for (Map.Entry entry : blockLocations.entrySet()) { + Location location = entry.getKey(); + WorldPosition worldPosition = WorldPosition.ofBlock(location); + + positions.put(worldPosition, entry.getValue()); + cachedLocations.put(worldPosition, location); + } + + this.viewerHandler = new ViewerHandler<>( + player -> positions, + this::sendUpdate, + player -> {}, + () -> new GroupPacketBlockViewer(positions, () -> positions, breakSpeed) + ); + + this.dataHandler = new DataHandler<>(this, this::sendUpdate, positions, breakSpeed); + + this.metadataHandler = new MetadataHandler(); + } + + public List getBlockStates(@NonNull Player player) { + List states = new ArrayList<>(); + + GroupPacketBlockViewer viewer = getViewer(player).orElse(null); + + if(viewer == null) { + for (Map.Entry entry : getData().entrySet()) { + Location location = cachedLocations.get(entry.getKey()); + states.add(entry.getValue().createBlockState().copy(location)); + } + + return states; + } + + Map data = viewer.getData() == null ? viewer.getDataSupplier().get() : viewer.getData(); + + for (Map.Entry entry : data.entrySet()) { + Location location = cachedLocations.get(entry.getKey()); + states.add(entry.getValue().createBlockState().copy(location)); + } + + return states; + } + + /** + * Sends a block update to the specified player at the current location. + * + * @param player the player to whom the block update will be sent + */ + public void sendUpdate(@NonNull Player player) { + player.sendBlockChanges(getBlockStates(player)); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHandler.java b/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHandler.java new file mode 100644 index 0000000..1255115 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHandler.java @@ -0,0 +1,81 @@ +package net.bitbylogic.packetblocks.metadata; + +import lombok.NonNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; + +public class MetadataHandler { + + private final HashMap metadata = new HashMap<>(); + + /** + * Adds a metadata entry with the specified key and object. + * If the key already exists in the metadata map, the method does nothing. + * + * @param key the key used to identify the metadata entry; must not be null + * @param object the object to be associated with the key; must not be null + */ + protected void addMetadata(@NonNull String key, @NonNull Object object) { + if (metadata.containsKey(key)) { + return; + } + + metadata.put(key, object); + } + + /** + * Removes metadata associated with the specified key from the metadata map. + * + * @param key the key whose associated metadata is to be removed. Must not be null. + */ + protected void removeMetadata(@NonNull String key) { + metadata.remove(key); + } + + /** + * Checks if a metadata entry with the specified key exists. + * + * @param key the key to check for metadata presence, must not be null + * @return true if metadata with the given key exists, false otherwise + */ + protected boolean hasMetadata(@NonNull String key) { + return metadata.containsKey(key); + } + + /** + * Retrieves the metadata associated with the specified key. + * + * @param key the key to look up in the metadata map; must not be null. + * @return the metadata value associated with the given key, or null if no value is present for the key. + */ + protected Object getMetadata(@NonNull String key) { + return getMetadata(key, null); + } + + /** + * Retrieves the metadata associated with the given key and casts it to the specified type. + * + * @param key the key for the metadata entry, must not be null + * @param clazz the class type to cast the metadata value to, must not be null + * @return the metadata value associated with the provided key, cast to the desired type, + * or null if the key does not exist or the value could not be cast + * @throws ClassCastException if the metadata value cannot be cast to the specified type + */ + protected T getMetadataAs(@NonNull String key, @NonNull Class clazz) { + return (T) getMetadata(key, null); + } + + /** + * Retrieves the metadata associated with the given key. If no metadata exists for the key, + * returns the specified fallback value. + * + * @param key the key whose associated metadata is to be returned. Must not be null. + * @param fallback the value to return if no metadata is found for the given key. May be null. + * @return the metadata associated with the specified key, or the fallback value if no metadata is found. + */ + protected Object getMetadata(@NonNull String key, @Nullable Object fallback) { + return metadata.getOrDefault(key, fallback); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHolder.java b/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHolder.java new file mode 100644 index 0000000..f36b2e0 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/metadata/MetadataHolder.java @@ -0,0 +1,75 @@ +package net.bitbylogic.packetblocks.metadata; + +import lombok.NonNull; +import org.jetbrains.annotations.Nullable; + +public interface MetadataHolder { + + @NonNull MetadataHandler getMetadataHandler(); + + /** + * Adds a metadata entry with the specified key and object. + * If the key already exists in the metadata map, the method does nothing. + * + * @param key the key used to identify the metadata entry; must not be null + * @param object the object to be associated with the key; must not be null + */ + default void addMetadata(@NonNull String key, @NonNull Object object) { + getMetadataHandler().addMetadata(key, object); + } + + /** + * Removes metadata associated with the specified key from the metadata map. + * + * @param key the key whose associated metadata is to be removed. Must not be null. + */ + default void removeMetadata(@NonNull String key) { + getMetadataHandler().removeMetadata(key); + } + + /** + * Checks if a metadata entry with the specified key exists. + * + * @param key the key to check for metadata presence, must not be null + * @return true if metadata with the given key exists, false otherwise + */ + default boolean hasMetadata(@NonNull String key) { + return getMetadataHandler().hasMetadata(key); + } + + /** + * Retrieves the metadata associated with the specified key. + * + * @param key the key to look up in the metadata map; must not be null. + * @return the metadata value associated with the given key, or null if no value is present for the key. + */ + default Object getMetadata(@NonNull String key) { + return getMetadataHandler().getMetadata(key); + } + + /** + * Retrieves the metadata associated with the given key and casts it to the specified type. + * + * @param key the key for the metadata entry, must not be null + * @param clazz the class type to cast the metadata value to, must not be null + * @return the metadata value associated with the provided key, cast to the desired type, + * or null if the key does not exist or the value could not be cast + * @throws ClassCastException if the metadata value cannot be cast to the specified type + */ + default T getMetadataAs(@NonNull String key, @NonNull Class clazz) { + return (T) getMetadataHandler().getMetadata(key, null); + } + + /** + * Retrieves the metadata associated with the given key. If no metadata exists for the key, + * returns the specified fallback value. + * + * @param key the key whose associated metadata is to be returned. Must not be null. + * @param fallback the value to return if no metadata is found for the given key. May be null. + * @return the metadata associated with the specified key, or the fallback value if no metadata is found. + */ + default Object getMetadata(@NonNull String key, @Nullable Object fallback) { + return getMetadataHandler().getMetadata(key, fallback); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/viewer/PacketBlockViewer.java b/src/main/java/net/bitbylogic/packetblocks/viewer/PacketBlockViewer.java new file mode 100644 index 0000000..ee2a53c --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/viewer/PacketBlockViewer.java @@ -0,0 +1,32 @@ +package net.bitbylogic.packetblocks.viewer; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import net.bitbylogic.packetblocks.metadata.MetadataHandler; +import net.bitbylogic.packetblocks.metadata.MetadataHolder; + +import java.util.function.Supplier; + +@Setter +@Getter +@AllArgsConstructor +public class PacketBlockViewer implements MetadataHolder { + + private final MetadataHandler metadataHandler = new MetadataHandler(); + + private T data; + private Supplier dataSupplier; + private int breakSpeed; + + public T getSuppliedData() { + return dataSupplier != null ? dataSupplier.get() : data; + } + + @Override + public @NonNull MetadataHandler getMetadataHandler() { + return metadataHandler; + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHandler.java b/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHandler.java new file mode 100644 index 0000000..a301a3a --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHandler.java @@ -0,0 +1,209 @@ +package net.bitbylogic.packetblocks.viewer; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.bitbylogic.packetblocks.block.PacketBlockPlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +@RequiredArgsConstructor +public class ViewerHandler> { + + private final Set> viewConditions = new HashSet<>(); + private final HashMap viewers = new HashMap<>(); + + private final Function playerDataSupplier; + + private final Consumer updateConsumer; + private final Consumer removeConsumer; + + private final Supplier dataSupplier; + + protected Map getViewers() { + return Collections.unmodifiableMap(viewers); + } + + /** + * Checks if the specified player meets all the conditions required to view this Packet Block. + * The conditions are evaluated using the stream of view requirements associated with this block. + * + * @param player The player whose eligibility to view the block is being checked. Must not be null. + * @return True if the player satisfies all the view conditions, otherwise false. + */ + protected boolean canView(@NonNull Player player) { + return viewConditions.stream().allMatch(viewRequirement -> viewRequirement.test(player)); + } + + /** + * Checks whether the specified player is a viewer of this Packet Block. + * + * @param player The player to check. Must not be null. + * @return True if the player is a viewer of this Packet Block, false otherwise. + */ + protected boolean isViewer(@NonNull Player player) { + return viewers.containsKey(player.getUniqueId()); + } + + /** + * Retrieves the {@link PacketBlockPlayerData} associated with the given player if they are a viewer + * of this Packet Block. + * + * @param player The player whose viewer data is to be retrieved. Must not be null. + * @return An {@link Optional} containing the {@link PacketBlockPlayerData} associated with the player + * if they are a viewer, or an empty optional if they are not a viewer. + */ + protected Optional getViewer(@NonNull Player player) { + return Optional.ofNullable(viewers.get(player.getUniqueId())); + } + + /** + * Attempts to add the specified player as a viewer to this Packet Block if they meet the viewing conditions. + * If the player is successfully added, an optional containing the associated {@link T} + * instance is returned. If the player is already a viewer, their existing {@link T} is returned. + * No viewer is added if the player does not meet the viewing conditions. + * + * @param player The player to attempt to add as a viewer. + * @param sendUpdate Whether to send a block update to the player upon successfully adding them as a viewer. + * @return An {@link Optional} containing the {@link T} associated with the player + * if they meet the conditions and are added as a viewer, or an empty optional otherwise. + */ + protected Optional attemptAddViewer(@NonNull Player player, boolean sendUpdate) { + if (!canView(player)) { + return Optional.empty(); + } + + if(isViewer(player)) { + return getViewer(player); + } + + T data = dataSupplier.get(); + viewers.put(player.getUniqueId(), data); + + if(sendUpdate) { + updateConsumer.accept(player); + } + + return Optional.of(data); + } + + /** + * Adds the specified player as a viewer to this Packet Block. + * This method bypasses any view requirements that are set. + * + * @param player The player to add as a viewer. + * @return The {@link PacketBlockPlayerData} instance associated with the added player. + */ + protected T addViewer(@NonNull Player player) { + T data = dataSupplier.get(); + viewers.put(player.getUniqueId(), data); + return data; + } + + /** + * Adds the specified player as a viewer to this Packet Block if they are not already added + * and updates their block state to reflect the current state of the Packet Block. + * This method bypasses any view requirements that are set. + * + * @param player The player to add as a viewer and send block updates to + * @return The {@link PacketBlockPlayerData} instance associated with the added player + */ + protected T addAndUpdateViewer(@NonNull Player player) { + T data = addViewer(player); + playerDataSupplier.apply(player); + return data; + } + + /** + * Removes the specified player from the list of viewers if they are present. + * If the player is successfully removed, it sends a block change notification + * to the player for a specific location. + * + * @param player the player to be removed from the viewers list; must not be null + */ + protected void removeViewer(@NonNull Player player) { + if(!viewers.containsKey(player.getUniqueId())) { + return; + } + + viewers.remove(player.getUniqueId()); + removeConsumer.accept(player); + } + + /** + * Adds a viewing condition to the list of conditions if it is not already present. + * + * @param condition the condition to be added, represented as a {@code Predicate}. + * This condition evaluates to determine whether a player meets the viewing criteria. + */ + protected void addViewCondition(@NonNull Predicate condition) { + if(viewConditions.contains(condition)) { + return; + } + + viewConditions.add(condition); + } + + /** + * Adds a metadata key-value pair to the specific player's {@link PacketBlockPlayerData}, if the player is a viewer. + * + * @param player The player whose metadata is being updated. Must not be null. + * @param key The key for the metadata entry. Must not be null. + * @param object The value associated with the specified key. Must not be null. + */ + protected void addMetadata(@NonNull Player player, @NonNull String key, @NonNull Object object) { + getViewer(player).ifPresent(playerData -> playerData.addMetadata(key, object)); + } + + /** + * Removes the metadata associated with a specific key for the given player, if the player is a viewer + * of this PacketBlock. This allows targeted removal of metadata entries tied to individual players. + * + * @param player The player whose associated metadata is to be removed. Must not be null. + * @param key The key of the metadata to be removed. Must not be null. + */ + protected void removeMetadata(@NonNull Player player, @NonNull String key) { + getViewer(player).ifPresent(playerData -> playerData.removeMetadata(key)); + } + + /** + * Checks if the specified player has metadata associated with the given key within this Packet Block. + * + * @param player The player whose metadata association is to be checked. Must not be null. + * @param key The key of the metadata to check. Must not be null. + * @return True if the specified player has metadata associated with the given key, otherwise false. + */ + protected boolean hasMetadata(@NonNull Player player, @NonNull String key) { + return getViewer(player).map(playerData -> playerData.hasMetadata(key)).orElse(false); + } + + /** + * Retrieves the metadata associated with a specific player and a given key. + * + * @param player The player for whom metadata is being retrieved. Must not be null. + * @param key The key identifying the metadata to be retrieved. Must not be null. + * @return The metadata object associated with the specified player and key, or null if no metadata exists for the key. + */ + protected Object getMetadata(@NonNull Player player, @NonNull String key) { + return getMetadataAs(player, key, null); + } + + /** + * Retrieves metadata associated with a given key for a specified player. If no metadata is found, + * a fallback value will be returned. + * + * @param player the player whose metadata is to be retrieved; must not be null + * @param key the key associated with the metadata to retrieve; must not be null + * @param fallback the value to return if no associated metadata is found; can be null + * @return the metadata value associated with the specified key, or the fallback value if no metadata is found + */ + protected Object getMetadataAs(@NonNull Player player, @NonNull String key, @Nullable Object fallback) { + return getViewer(player).map(playerData -> playerData.getMetadata(key)).orElse(fallback); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHolder.java b/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHolder.java new file mode 100644 index 0000000..78da9db --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/viewer/ViewerHolder.java @@ -0,0 +1,170 @@ +package net.bitbylogic.packetblocks.viewer; + +import lombok.NonNull; +import net.bitbylogic.packetblocks.block.PacketBlockPlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Predicate; + +public interface ViewerHolder> { + + ViewerHandler getViewerHandler(); + + default Map getViewers() { + return getViewerHandler().getViewers(); + } + + /** + * Checks if the specified player meets all the conditions required to view this Packet Block. + * The conditions are evaluated using the stream of view requirements associated with this block. + * + * @param player The player whose eligibility to view the block is being checked. Must not be null. + * @return True if the player satisfies all the view conditions, otherwise false. + */ + default boolean canView(@NonNull Player player) { + return getViewerHandler().canView(player); + } + + /** + * Checks whether the specified player is a viewer of this Packet Block. + * + * @param player The player to check. Must not be null. + * @return True if the player is a viewer of this Packet Block, false otherwise. + */ + default boolean isViewer(@NonNull Player player) { + return getViewerHandler().isViewer(player); + } + + /** + * Retrieves the {@link PacketBlockPlayerData} associated with the given player if they are a viewer + * of this Packet Block. + * + * @param player The player whose viewer data is to be retrieved. Must not be null. + * @return An {@link Optional} containing the {@link PacketBlockPlayerData} associated with the player + * if they are a viewer, or an empty optional if they are not a viewer. + */ + default Optional getViewer(@NonNull Player player) { + return getViewerHandler().getViewer(player); + } + + /** + * Attempts to add the specified player as a viewer to this Packet Block if they meet the viewing conditions. + * If the player is successfully added, an optional containing the associated {@link T} + * instance is returned. If the player is already a viewer, their existing {@link T} is returned. + * No viewer is added if the player does not meet the viewing conditions. + * + * @param player The player to attempt to add as a viewer. + * @param sendUpdate Whether to send a block update to the player upon successfully adding them as a viewer. + * @return An {@link Optional} containing the {@link T} associated with the player + * if they meet the conditions and are added as a viewer, or an empty optional otherwise. + */ + default Optional attemptAddViewer(@NonNull Player player, boolean sendUpdate) { + return getViewerHandler().attemptAddViewer(player, sendUpdate); + } + + /** + * Adds the specified player as a viewer to this Packet Block. + * This method bypasses any view requirements that are set. + * + * @param player The player to add as a viewer. + * @return The {@link PacketBlockPlayerData} instance associated with the added player. + */ + default T addViewer(@NonNull Player player) { + return getViewerHandler().addViewer(player); + } + + /** + * Adds the specified player as a viewer to this Packet Block if they are not already added + * and updates their block state to reflect the current state of the Packet Block. + * This method bypasses any view requirements that are set. + * + * @param player The player to add as a viewer and send block updates to + * @return The {@link PacketBlockPlayerData} instance associated with the added player + */ + default T addAndUpdateViewer(@NonNull Player player) { + return getViewerHandler().addAndUpdateViewer(player); + } + + /** + * Adds a viewing condition to the list of conditions if it is not already present. + * + * @param condition the condition to be added, represented as a {@code Predicate}. + * This condition evaluates to determine whether a player meets the viewing criteria. + */ + default void addViewCondition(@NonNull Predicate condition) { + getViewerHandler().addViewCondition(condition); + } + + /** + * Removes the specified player from the list of viewers if they are present. + * If the player is successfully removed, it sends a block change notification + * to the player for a specific location. + * + * @param player the player to be removed from the viewers list; must not be null + */ + default void removeViewer(@NonNull Player player) { + getViewerHandler().removeViewer(player); + } + + /** + * Adds a metadata key-value pair to the specific player's {@link PacketBlockPlayerData}, if the player is a viewer. + * + * @param player The player whose metadata is being updated. Must not be null. + * @param key The key for the metadata entry. Must not be null. + * @param object The value associated with the specified key. Must not be null. + */ + default void addMetadata(@NonNull Player player, @NonNull String key, @NonNull Object object) { + getViewerHandler().addMetadata(player, key, object); + } + + /** + * Removes the metadata associated with a specific key for the given player, if the player is a viewer + * of this PacketBlock. This allows targeted removal of metadata entries tied to individual players. + * + * @param player The player whose associated metadata is to be removed. Must not be null. + * @param key The key of the metadata to be removed. Must not be null. + */ + default void removeMetadata(@NonNull Player player, @NonNull String key) { + getViewerHandler().removeMetadata(player, key); + } + + /** + * Checks if the specified player has metadata associated with the given key within this Packet Block. + * + * @param player The player whose metadata association is to be checked. Must not be null. + * @param key The key of the metadata to check. Must not be null. + * @return True if the specified player has metadata associated with the given key, otherwise false. + */ + default boolean hasMetadata(@NonNull Player player, @NonNull String key) { + return getViewerHandler().hasMetadata(player, key); + } + + /** + * Retrieves the metadata associated with a specific player and a given key. + * + * @param player The player for whom metadata is being retrieved. Must not be null. + * @param key The key identifying the metadata to be retrieved. Must not be null. + * @return The metadata object associated with the specified player and key, or null if no metadata exists for the key. + */ + default Object getMetadata(@NonNull Player player, @NonNull String key) { + return getViewerHandler().getMetadata(player, key); + } + + /** + * Retrieves metadata associated with a given key for a specified player. If no metadata is found, + * a fallback value will be returned. + * + * @param player the player whose metadata is to be retrieved; must not be null + * @param key the key associated with the metadata to retrieve; must not be null + * @param fallback the value to return if no associated metadata is found; can be null + * @return the metadata value associated with the specified key, or the fallback value if no metadata is found + */ + default Object getMetadataAs(@NonNull Player player, @NonNull String key, @Nullable Object fallback) { + return getViewerHandler().getMetadataAs(player, key, fallback); + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/viewer/impl/GroupPacketBlockViewer.java b/src/main/java/net/bitbylogic/packetblocks/viewer/impl/GroupPacketBlockViewer.java new file mode 100644 index 0000000..46f1def --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/viewer/impl/GroupPacketBlockViewer.java @@ -0,0 +1,32 @@ +package net.bitbylogic.packetblocks.viewer.impl; + +import lombok.NonNull; +import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; +import net.bitbylogic.utils.location.WorldPosition; +import org.bukkit.block.data.BlockData; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class GroupPacketBlockViewer extends PacketBlockViewer> { + + public GroupPacketBlockViewer(Map data, Supplier> dataSupplier, int breakSpeed) { + super(new HashMap<>(data), dataSupplier, breakSpeed); + } + + public void setBlockData(@NonNull WorldPosition position, @NonNull BlockData blockData) { + if(!getData().containsKey(position)) { + throw new IllegalArgumentException("Position " + position + " is not part of this group"); + } + + getData().put(position, blockData); + } + + public void setGroupBlockData(@NonNull BlockData blockData) { + for (Map.Entry entry : getData().entrySet()) { + entry.setValue(blockData); + } + } + +} diff --git a/src/main/java/net/bitbylogic/packetblocks/viewer/impl/SinglePacketBlockViewer.java b/src/main/java/net/bitbylogic/packetblocks/viewer/impl/SinglePacketBlockViewer.java new file mode 100644 index 0000000..51b56e0 --- /dev/null +++ b/src/main/java/net/bitbylogic/packetblocks/viewer/impl/SinglePacketBlockViewer.java @@ -0,0 +1,14 @@ +package net.bitbylogic.packetblocks.viewer.impl; + +import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; +import org.bukkit.block.data.BlockData; + +import java.util.function.Supplier; + +public class SinglePacketBlockViewer extends PacketBlockViewer { + + public SinglePacketBlockViewer(BlockData data, Supplier dataSupplier, int breakSpeed) { + super(data, dataSupplier, breakSpeed); + } + +} From 83db768dcea7ac9a586a52f7c1915a60911f1b24 Mon Sep 17 00:00:00 2001 From: Justin <33465177+BitByLogics@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:08:36 -0500 Subject: [PATCH 2/3] feat(group): Implement group support --- .../adapter/BlockBreakAdapter.java | 38 +++- .../adapter/BlockPlaceAdapter.java | 5 +- .../adapter/BlockUpdateAdapter.java | 58 ++++- .../adapter/ChunkLoadAdapter.java | 77 +++++-- .../packetblocks/block/PacketBlock.java | 27 ++- .../packetblocks/block/PacketBlockHolder.java | 10 + .../block/PacketBlockManager.java | 201 ++++++++++++------ .../packetblocks/data/DataHandler.java | 42 +++- .../packetblocks/data/DataHolder.java | 11 + .../event/PacketBlockBreakEvent.java | 10 +- .../event/PacketBlockInteractEvent.java | 10 +- .../event/PacketBlockStartBreakEvent.java | 8 +- .../packetblocks/group/PacketBlockGroup.java | 74 ++++++- .../listener/PacketBlockListener.java | 24 ++- .../task/BlockAnimationContext.java | 5 +- .../task/PacketBlockAnimationTask.java | 39 +++- .../packetblocks/util/PacketBlockUtil.java | 98 ++++++++- 17 files changed, 566 insertions(+), 171 deletions(-) diff --git a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java index e5b0e7a..1f9ebe1 100644 --- a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java +++ b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockBreakAdapter.java @@ -1,6 +1,5 @@ package net.bitbylogic.packetblocks.adapter; -import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketListener; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; @@ -9,13 +8,17 @@ import lombok.NonNull; import net.bitbylogic.packetblocks.PacketBlocks; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; +import net.bitbylogic.packetblocks.block.PacketBlockManager; import net.bitbylogic.packetblocks.event.PacketBlockBreakEvent; import net.bitbylogic.packetblocks.event.PacketBlockStartBreakEvent; -import net.bitbylogic.packetblocks.block.PacketBlockManager; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; import net.bitbylogic.packetblocks.task.PacketBlockAnimationTask; +import net.bitbylogic.packetblocks.util.PacketBlockUtil; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -46,14 +49,14 @@ public void onPacketReceive(PacketReceiveEvent event) { Vector3i position = packet.getBlockPosition(); Location location = new Location(player.getWorld(), position.getX(), position.getY(), position.getZ()); - Optional optionalBlock = manager.getBlock(location); + Optional> optionalBlock = manager.getBlock(location); if (optionalBlock.isEmpty()) return; - PacketBlock packetBlock = optionalBlock.get(); + PacketBlockHolder packetBlock = optionalBlock.get(); if (!packetBlock.isViewer(player)) return; int breakSpeed = packetBlock.getBreakSpeed(player); - float vanillaHardness = packetBlock.getBlockState(player).getType().getHardness(); + float vanillaHardness = PacketBlockUtil.getBlockType(player, location).getHardness(); switch (packet.getAction()) { case START_DIGGING -> handleStartDestroy(player, packetBlock, location, packet, breakSpeed, vanillaHardness); @@ -65,7 +68,7 @@ public void onPacketReceive(PacketReceiveEvent event) { } private void handleStartDestroy(@NonNull Player player, - @NonNull PacketBlock packetBlock, + @NonNull PacketBlockHolder packetBlock, @NonNull Location location, @NonNull WrapperPlayClientPlayerDigging packet, int breakSpeed, @@ -91,7 +94,7 @@ private void handleStartDestroy(@NonNull Player player, } private void handleStopDestroy(@NonNull Player player, - @NonNull PacketBlock packetBlock, + @NonNull PacketBlockHolder packetBlock, @NonNull Location location) { Bukkit.getScheduler().runTask(PacketBlocks.getInstance(), () -> { @@ -106,7 +109,26 @@ private void handleStopDestroy(@NonNull Player player, } if (breakEvent.isDropItems()) { - packetBlock.getBlockState(player).getBlock() + if (packetBlock instanceof PacketBlock singleBlock) { + singleBlock.getBlockState(player).getBlock() + .getDrops(player.getInventory().getItemInMainHand(), player) + .forEach(drop -> player.getWorld().dropItemNaturally(location, drop)); + return; + } + + if(!(packetBlock instanceof PacketBlockGroup group)) { + return; + } + + Optional optionalBlockData = group.getDataAt(player, location); + + if (optionalBlockData.isEmpty()) { + return; + } + + BlockData blockData = optionalBlockData.get(); + + blockData.createBlockState().copy(location).getBlock() .getDrops(player.getInventory().getItemInMainHand(), player) .forEach(drop -> player.getWorld().dropItemNaturally(location, drop)); } diff --git a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockPlaceAdapter.java b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockPlaceAdapter.java index aeaae7c..c3923b6 100644 --- a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockPlaceAdapter.java +++ b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockPlaceAdapter.java @@ -7,6 +7,7 @@ import com.github.retrooper.packetevents.util.Vector3i; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement; import lombok.RequiredArgsConstructor; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.block.PacketBlockManager; import net.bitbylogic.packetblocks.block.PacketBlock; import net.bitbylogic.packetblocks.util.PacketBlockUtil; @@ -64,13 +65,13 @@ public void onPacketReceive(PacketReceiveEvent event) { return; } - Optional optionalBlock = manager.getBlock(location); + Optional> optionalBlock = manager.getBlock(location); if (optionalBlock.isEmpty()) { return; } - PacketBlock packetBlock = optionalBlock.get(); + PacketBlockHolder packetBlock = optionalBlock.get(); if (!packetBlock.isViewer(player)) { return; diff --git a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockUpdateAdapter.java b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockUpdateAdapter.java index 4ba44ee..10b13f2 100644 --- a/src/main/java/net/bitbylogic/packetblocks/adapter/BlockUpdateAdapter.java +++ b/src/main/java/net/bitbylogic/packetblocks/adapter/BlockUpdateAdapter.java @@ -9,8 +9,11 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import lombok.RequiredArgsConstructor; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.block.PacketBlockManager; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; import org.bukkit.Location; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import java.util.ArrayList; @@ -38,11 +41,28 @@ public void onPacketSend(PacketSendEvent event) { blockPos.getZ() ); - Optional optionalBlock = manager.getBlock(bukkitLoc); + Optional> optionalBlock = manager.getBlock(bukkitLoc); if (optionalBlock.isPresent() && optionalBlock.get().isViewer(player)) { - PacketBlock block = optionalBlock.get(); - packet.setBlockState(WrappedBlockState.getByString(block.getBlockState(player).getBlockData().getAsString())); + PacketBlockHolder block = optionalBlock.get(); + + if(block instanceof PacketBlock singleBlock) { + packet.setBlockState(WrappedBlockState.getByString(singleBlock.getData(player).getAsString())); + return; + } + + if(!(block instanceof PacketBlockGroup group)) { + return; + } + + Optional optionalBlockData = group.getDataAt(player, bukkitLoc); + + if (optionalBlockData.isEmpty()) { + return; + } + + BlockData blockData = optionalBlockData.get(); + packet.setBlockState(WrappedBlockState.getByString(blockData.getAsString())); } } @@ -52,16 +72,36 @@ else if (event.getPacketType() == PacketType.Play.Server.MULTI_BLOCK_CHANGE) { WrapperPlayServerMultiBlockChange.EncodedBlock[] blocks = packet.getBlocks(); List modifiedBlocks = new ArrayList<>(); - for (WrapperPlayServerMultiBlockChange.EncodedBlock block : blocks) { - Location loc = new Location(player.getWorld(), block.getX(), block.getY(), block.getZ()); - Optional pb = manager.getBlock(loc); + for (WrapperPlayServerMultiBlockChange.EncodedBlock encodedBlock : blocks) { + Location loc = new Location(player.getWorld(), encodedBlock.getX(), encodedBlock.getY(), encodedBlock.getZ()); + Optional> pb = manager.getBlock(loc); if (pb.isPresent() && pb.get().isViewer(player)) { + PacketBlockHolder block = pb.get(); + + if(block instanceof PacketBlock singleBlock) { + modifiedBlocks.add(new WrapperPlayServerMultiBlockChange.EncodedBlock( + WrappedBlockState.getByString(singleBlock.getData(player).getAsString()), + encodedBlock.getX(), encodedBlock.getY(), encodedBlock.getZ())); + return; + } + + if(!(block instanceof PacketBlockGroup group)) { + return; + } + + Optional optionalBlockData = group.getDataAt(player, loc); + + if (optionalBlockData.isEmpty()) { + return; + } + + BlockData blockData = optionalBlockData.get(); modifiedBlocks.add(new WrapperPlayServerMultiBlockChange.EncodedBlock( - WrappedBlockState.getByString(pb.get().getBlockState(player).getBlockData().getAsString()), - block.getX(), block.getY(), block.getZ())); + WrappedBlockState.getByString(blockData.getAsString()), + encodedBlock.getX(), encodedBlock.getY(), encodedBlock.getZ())); } else { - modifiedBlocks.add(block); + modifiedBlocks.add(encodedBlock); } } diff --git a/src/main/java/net/bitbylogic/packetblocks/adapter/ChunkLoadAdapter.java b/src/main/java/net/bitbylogic/packetblocks/adapter/ChunkLoadAdapter.java index 61d012b..b097a62 100644 --- a/src/main/java/net/bitbylogic/packetblocks/adapter/ChunkLoadAdapter.java +++ b/src/main/java/net/bitbylogic/packetblocks/adapter/ChunkLoadAdapter.java @@ -9,13 +9,17 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData; import lombok.RequiredArgsConstructor; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.block.PacketBlockManager; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; +import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Location; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.List; +import java.util.Map; @RequiredArgsConstructor public class ChunkLoadAdapter implements PacketListener { @@ -32,38 +36,71 @@ public void onPacketSend(PacketSendEvent event) { int chunkX = packet.getColumn().getX(); int chunkZ = packet.getColumn().getZ(); - List blocks = new ArrayList<>(manager.getBlocks(player.getWorld(), chunkX, chunkZ).values()); + List> blocks = new ArrayList<>(manager.getBlocks(player.getWorld(), chunkX, chunkZ).values()); if (blocks.isEmpty()) return; BaseChunk[] sections = packet.getColumn().getChunks(); int absMinHeight = Math.abs(player.getWorld().getMinHeight()); - for (PacketBlock packetBlock : blocks) { + for (PacketBlockHolder packetBlock : blocks) { if (!packetBlock.isViewer(player)) continue; - Location loc = packetBlock.getLocation(); - int xInChunk = loc.getBlockX() & 0xF; - int y = loc.getBlockY(); - int zInChunk = loc.getBlockZ() & 0xF; + if(packetBlock instanceof PacketBlock singleBlock) { + Location loc = singleBlock.getLocation(); + int xInChunk = loc.getBlockX() & 0xF; + int y = loc.getBlockY(); + int zInChunk = loc.getBlockZ() & 0xF; - int sectionIndex = (y >> 4) + (absMinHeight >> 4); - int yInSection = y & 0xF; + int sectionIndex = (y >> 4) + (absMinHeight >> 4); + int yInSection = y & 0xF; - if (sectionIndex < 0 || sectionIndex >= sections.length) continue; + if (sectionIndex < 0 || sectionIndex >= sections.length) continue; - BaseChunk section = sections[sectionIndex]; - if (section == null) continue; + BaseChunk section = sections[sectionIndex]; + if (section == null) continue; - BlockData bukkitData = packetBlock.getBlockState(player).getBlockData(); - WrappedBlockState wrappedState = WrappedBlockState.getByString(bukkitData.getAsString()); + BlockData bukkitData = singleBlock.getData(player); + WrappedBlockState wrappedState = WrappedBlockState.getByString(bukkitData.getAsString()); - section.set( - PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), - xInChunk, - yInSection, - zInChunk, - wrappedState.getGlobalId() - ); + section.set( + PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), + xInChunk, + yInSection, + zInChunk, + wrappedState.getGlobalId() + ); + + continue; + } + + if(!(packetBlock instanceof PacketBlockGroup group)) { + continue; + } + + for (Map.Entry entry : group.getData().entrySet()) { + Location loc = group.getCachedLocations().get(entry.getKey()); + int xInChunk = loc.getBlockX() & 0xF; + int y = loc.getBlockY(); + int zInChunk = loc.getBlockZ() & 0xF; + + int sectionIndex = (y >> 4) + (absMinHeight >> 4); + int yInSection = y & 0xF; + + if (sectionIndex < 0 || sectionIndex >= sections.length) continue; + + BaseChunk section = sections[sectionIndex]; + if (section == null) continue; + + WrappedBlockState wrappedState = WrappedBlockState.getByString(entry.getValue().getAsString()); + + section.set( + PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), + xInChunk, + yInSection, + zInChunk, + wrappedState.getGlobalId() + ); + } } } diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java index caa4c5c..3665ce6 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlock.java @@ -13,15 +13,13 @@ import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.Nullable; -import java.util.List; - @Getter public class PacketBlock implements PacketBlockHolder { @@ -30,8 +28,6 @@ public class PacketBlock implements PacketBlockHolder boundingBoxes; - private final DataHandler dataHandler; private final ViewerHandler viewerHandler; private final MetadataHandler metadataHandler; @@ -61,18 +57,16 @@ protected PacketBlock(@NonNull Location location, @NonNull BlockData blockData, this.position = WorldPosition.ofBlock(location); this.chunk = position.toChunkPosition(); - this.location = location; - - this.boundingBoxes = BoundingBoxes.getBoxes(blockData); + this.location = location.toBlockLocation(); this.viewerHandler = new ViewerHandler<>( player -> blockData, this::sendUpdate, - player -> player.sendBlockChange(location, location.getBlock().getBlockData()), + player -> player.sendBlockChange(this.location, location.getBlock().getBlockData()), () -> new SinglePacketBlockViewer(blockData, () -> blockData, breakSpeed) ); - this.dataHandler = new DataHandler<>(this, this::sendUpdate, blockData, breakSpeed); + this.dataHandler = new DataHandler<>(this, this::sendUpdate, BoundingBoxes::getBoxes, blockData, breakSpeed); this.metadataHandler = new MetadataHandler(); } @@ -98,8 +92,9 @@ public BlockState getBlockState(@NonNull Player player) { * * @param player the player to whom the block update will be sent */ + @Override public void sendUpdate(@NonNull Player player) { - player.sendBlockChange(location, getBlockState(player).getBlockData()); + player.sendBlockChange(location, getData(player)); } /** @@ -139,4 +134,14 @@ public void simulateBreak(@NonNull Player player, @Nullable ItemStack tool) { }); } + @Override + public boolean existsIn(@NonNull World world) { + return position.worldName().equalsIgnoreCase(world.getName()); + } + + @Override + public boolean existsAt(@NonNull Location location) { + return position.equals(WorldPosition.ofBlock(location)); + } + } diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java index 5768210..92dc648 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockHolder.java @@ -1,10 +1,20 @@ package net.bitbylogic.packetblocks.block; +import lombok.NonNull; import net.bitbylogic.packetblocks.data.DataHolder; import net.bitbylogic.packetblocks.metadata.MetadataHolder; import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; import net.bitbylogic.packetblocks.viewer.ViewerHolder; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; public interface PacketBlockHolder> extends DataHolder, ViewerHolder, MetadataHolder { + void sendUpdate(@NonNull Player player); + + boolean existsIn(@NonNull World world); + + boolean existsAt(@NonNull Location location); + } diff --git a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java index ec74633..ff59893 100644 --- a/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java +++ b/src/main/java/net/bitbylogic/packetblocks/block/PacketBlockManager.java @@ -4,13 +4,14 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import net.bitbylogic.packetblocks.PacketBlocks; +import net.bitbylogic.packetblocks.data.DataHolder; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; import net.bitbylogic.utils.location.ChunkPosition; import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; @@ -25,7 +26,7 @@ @Getter public class PacketBlockManager { - private final ConcurrentHashMap> blockLocations = new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> blockLocations = new ConcurrentHashMap<>(); private final PacketBlocks plugin; @@ -55,7 +56,7 @@ public PacketBlock createBlock(@NonNull Location location, @NonNull BlockData bl ChunkPosition identifier = new ChunkPosition(location.getWorld().getName(), chunk.getX(), chunk.getZ()); if (blockLocations.containsKey(identifier)) { - Map blocks = blockLocations.get(identifier); + Map> blocks = blockLocations.get(identifier); if (blocks.containsValue(packetBlock)) { return packetBlock; @@ -66,13 +67,49 @@ public PacketBlock createBlock(@NonNull Location location, @NonNull BlockData bl return packetBlock; } - Map newBlocks = new HashMap<>(); + Map> newBlocks = new HashMap<>(); newBlocks.put(packetBlock.getPosition(), packetBlock); blockLocations.put(identifier, newBlocks); return packetBlock; } + public PacketBlockGroup createGroup(@NonNull Map groupBlocks) { + PacketBlockGroup packetGroup = new PacketBlockGroup(groupBlocks); + + for (Map.Entry> entry : packetGroup.getChunkPositions().entrySet()) { + ChunkPosition chunkPosition = entry.getKey(); + List worldPositions = entry.getValue(); + + Map> blocks = blockLocations.computeIfAbsent(chunkPosition, k -> new HashMap<>()); + + for (WorldPosition worldPosition : worldPositions) { + blocks.put(worldPosition, packetGroup); + blockLocations.put(chunkPosition, blocks); + } + } + + return packetGroup; + } + + public Optional getBlockData(@Nullable Player player, @NonNull Location location) { + PacketBlockHolder packetBlock = getBlock(location).orElse(null); + + if (packetBlock == null) { + return Optional.empty(); + } + + if (packetBlock instanceof PacketBlock singleBlock) { + return Optional.of(singleBlock.getData(player)); + } + + if (!(packetBlock instanceof PacketBlockGroup group)) { + return Optional.empty(); + } + + return group.getDataAt(player, location); + } + /** * Removes the specified {@link PacketBlock} from the blockLocations map and updates its visual * state for all associated viewers. If the {@link PacketBlock} exists in any chunk's block list, @@ -81,29 +118,47 @@ public PacketBlock createBlock(@NonNull Location location, @NonNull BlockData bl * * @param packetBlock the {@link PacketBlock} to be removed; must not be null */ - public void removeBlock(@NonNull PacketBlock packetBlock) { - ChunkPosition chunk = packetBlock.getChunk(); + public void removeBlock(@NonNull PacketBlockHolder packetBlock) { + for (UUID uuid : new ArrayList<>(packetBlock.getViewers().keySet())) { + Player player = Bukkit.getPlayer(uuid); + + if (player != null) { + packetBlock.removeViewer(player); + } + } - Map blocks = blockLocations.get(chunk); + if(packetBlock instanceof PacketBlock singleBlock) { + ChunkPosition chunk = singleBlock.getChunk(); - if (blocks == null) { + Map> blocks = blockLocations.get(chunk); + + if (blocks == null) { + return; + } + + blocks.remove(singleBlock.getPosition()); + return; + } + + if (!(packetBlock instanceof PacketBlockGroup group)) { return; } - blocks.remove(packetBlock.getPosition()); + for (Map.Entry> entry : group.getChunkPositions().entrySet()) { + ChunkPosition chunkPosition = entry.getKey(); + List worldPositions = entry.getValue(); - packetBlock.getViewers().keySet().forEach(uuid -> { - Player player = Bukkit.getPlayer(uuid); + Map> blocks = blockLocations.get(chunkPosition); - if (player != null) { - player.sendBlockChange( - packetBlock.getLocation(), - packetBlock.getLocation().getBlock().getBlockData() - ); + if (blocks == null) { + return; } - }); - } + for (WorldPosition worldPosition : worldPositions) { + blocks.remove(worldPosition); + } + } + } /** * Removes {@link PacketBlock} instances from the managed collection if they satisfy a specified condition. @@ -113,23 +168,32 @@ public void removeBlock(@NonNull PacketBlock packetBlock) { * @param removePredicate the condition used to determine which {@link PacketBlock} instances should be removed; * must not be null. */ - public void removeIf(Predicate removePredicate) { - for (Map.Entry> entry : blockLocations.entrySet()) { - Map blocks = entry.getValue(); + public void removeIf(Predicate> removePredicate) { + List>>> chunkEntries = + new ArrayList<>(blockLocations.entrySet()); - blocks.values().stream().filter(removePredicate).forEach(packetBlock -> { - packetBlock.getViewers().keySet().forEach(uuid -> { - Player player = Bukkit.getPlayer(uuid); + for (Map.Entry>> entry : chunkEntries) { + Map> blocks = entry.getValue(); + + List> toRemove = new ArrayList<>(); - if (player == null) { - return; + for (PacketBlockHolder block : blocks.values()) { + if (removePredicate.test(block)) { + toRemove.add(block); + } + } + + for (PacketBlockHolder packetBlock : toRemove) { + for (UUID uuid : new ArrayList<>(packetBlock.getViewers().keySet())) { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + Bukkit.getScheduler().runTaskLater(plugin, () -> packetBlock.removeViewer(player), 1); } + } - Bukkit.getScheduler().runTaskLater(plugin, () -> player.sendBlockChange(packetBlock.getLocation(), packetBlock.getLocation().getBlock().getBlockData()), 1); - }); - }); + blocks.values().remove(packetBlock); + } - blocks.values().removeIf(removePredicate); blockLocations.put(entry.getKey(), blocks); } } @@ -141,7 +205,7 @@ public void removeIf(Predicate removePredicate) { * @param location the {@link Location} at which to find the {@link PacketBlock}; must not be null * @return an {@link Optional} containing the matching {@link PacketBlock}, or an empty {@link Optional} if none is found */ - public Optional getBlock(@NonNull Location location) { + public Optional> getBlock(@NonNull Location location) { World world = location.getWorld(); if (world == null) { @@ -151,7 +215,7 @@ public Optional getBlock(@NonNull Location location) { Chunk chunk = location.getChunk(); ChunkPosition chunkPosition = ChunkPosition.of(chunk); - Map blocks = blockLocations.get(chunkPosition); + Map> blocks = blockLocations.get(chunkPosition); if (blocks == null) { return Optional.empty(); @@ -167,18 +231,12 @@ public Optional getBlock(@NonNull Location location) { * @param world the world for which the packet blocks are being queried; must not be null * @return a list of {@link PacketBlock} instances that exist in the specified world */ - public List getBlocks(@NonNull World world) { - List blocks = new ArrayList<>(); + public List> getBlocks(@NonNull World world) { + List> blocks = new ArrayList<>(); blockLocations.values().forEach(packetBlocks -> { packetBlocks.values().forEach(block -> { - if(block == null || block.getLocation() == null) { - return; - } - - World blockWorld = block.getLocation().getWorld(); - - if (blockWorld == null || !blockWorld.getName().equalsIgnoreCase(world.getName())) { + if (!block.existsIn(world)) { return; } @@ -199,7 +257,7 @@ public List getBlocks(@NonNull World world) { * @return a list of {@link PacketBlock} instances within the specified chunk, * or an empty list if no blocks are found */ - public Map getBlocks(@NonNull World world, int chunkX, int chunkZ) { + public Map> getBlocks(@NonNull World world, int chunkX, int chunkZ) { ChunkPosition chunkIdentifier = new ChunkPosition(world.getName(), chunkX, chunkZ); return blockLocations.getOrDefault(chunkIdentifier, new HashMap<>()); } @@ -211,8 +269,8 @@ public Map getBlocks(@NonNull World world, int chunk * @param player the player for whom the visible blocks are being queried; must not be null * @return a list of {@link PacketBlock} instances that the specified player can view */ - public List getBlocksByViewer(@NonNull Player player) { - List blocks = new ArrayList<>(); + public List> getBlocksByViewer(@NonNull Player player) { + List> blocks = new ArrayList<>(); blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { @@ -236,8 +294,8 @@ public List getBlocksByViewer(@NonNull Player player) { * @return a list of {@link PacketBlock} instances that are both visible to the specified player * and contain the specified metadata key. */ - public List getBlocksByViewerWithMeta(@NonNull Player player, @NonNull String metaKey) { - List blocks = new ArrayList<>(); + public List> getBlocksByViewerWithMeta(@NonNull Player player, @NonNull String metaKey) { + List> blocks = new ArrayList<>(); blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { @@ -258,8 +316,8 @@ public List getBlocksByViewerWithMeta(@NonNull Player player, @NonN * @param key the metadata key to filter blocks by; must not be null. * @return a list of {@link PacketBlock} instances that contain the specified metadata key. */ - public List getBlocksByMetadata(@NonNull String key) { - List blocks = new ArrayList<>(); + public List> getBlocksByMetadata(@NonNull String key) { + List> blocks = new ArrayList<>(); blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { @@ -282,8 +340,8 @@ public List getBlocksByMetadata(@NonNull String key) { * @param boundingBox the bounding box used to filter the blocks; must not be null * @return a list of {@link PacketBlock} instances that overlap with the specified bounding box */ - public List getHitBlocks(@NonNull World world, @NonNull BoundingBox boundingBox) { - List blocks = new ArrayList<>(); + public List> getHitBlocks(@NonNull World world, @NonNull BoundingBox boundingBox) { + List> blocks = new ArrayList<>(); getChunksInBoundingBox(world, boundingBox).forEach(chunk -> { getBlocks(world, chunk.getX(), chunk.getZ()).values().forEach(block -> { @@ -311,16 +369,13 @@ public List getHitBlocks(@NonNull World world, @NonNull BoundingBox public Set getChunksInBoundingBox(World world, BoundingBox box) { Set chunks = new HashSet<>(); - // Calculate the chunk coordinates for the bounding box's min and max corners int minChunkX = (int) Math.floor(box.getMinX()) >> 4; int maxChunkX = (int) Math.floor(box.getMaxX()) >> 4; int minChunkZ = (int) Math.floor(box.getMinZ()) >> 4; int maxChunkZ = (int) Math.floor(box.getMaxZ()) >> 4; - // Iterate through all chunk coordinates in this range for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - // Add the chunk at (chunkX, chunkZ) to the set Chunk chunk = world.getChunkAt(chunkX, chunkZ); chunks.add(chunk); } @@ -338,8 +393,8 @@ public Set getChunksInBoundingBox(World world, BoundingBox box) { * @return a list of {@link PacketBlock} instances that are both visible to the player and * intersect with the specified bounding box */ - public List getHitBlocksByViewer(@NonNull Player player, @NonNull BoundingBox boundingBox) { - List blocks = new ArrayList<>(); + public List> getHitBlocksByViewer(@NonNull Player player, @NonNull BoundingBox boundingBox) { + List> blocks = new ArrayList<>(); blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { @@ -347,9 +402,17 @@ public List getHitBlocksByViewer(@NonNull Player player, @NonNull B return; } - BoundingBox box = BoundingBox.of(block.getLocation().clone(), block.getLocation().clone().add(1, 1, 1)); + boolean hit = false; + + for (BoundingBox box : block.getBoundingBoxes()) { + if(!box.overlaps(boundingBox)) { + continue; + } + + hit = true; + } - if (!box.overlaps(boundingBox)) { + if (!hit) { return; } @@ -370,8 +433,8 @@ public List getHitBlocksByViewer(@NonNull Player player, @NonNull B * @return a list of {@link PacketBlock} instances satisfying the conditions of being visible * to the player, located within the bounding box, and containing the specified metadata key. */ - public List getHitBlocksByViewerWithMeta(@NonNull Player player, @NonNull BoundingBox boundingBox, @NonNull String metaKey) { - List blocks = new ArrayList<>(); + public List> getHitBlocksByViewerWithMeta(@NonNull Player player, @NonNull BoundingBox boundingBox, @NonNull String metaKey) { + List> blocks = new ArrayList<>(); blockLocations.forEach((identifier, value) -> { value.values().forEach(block -> { @@ -383,9 +446,17 @@ public List getHitBlocksByViewerWithMeta(@NonNull Player player, @N return; } - BoundingBox box = BoundingBox.of(block.getLocation().clone(), block.getLocation().clone().add(1, 1, 1)); + boolean hit = false; - if (!box.overlaps(boundingBox)) { + for (BoundingBox box : block.getBoundingBoxes()) { + if(!box.overlaps(boundingBox)) { + continue; + } + + hit = true; + } + + if (!hit) { return; } @@ -417,16 +488,12 @@ public void updateBlocks(@NonNull Player player) { * if null, all relevant blocks are updated */ public void updateBlocksWithMeta(@NonNull Player player, @Nullable String metaKey) { - Set states = new HashSet<>(); - if (metaKey == null) { - getBlocksByViewer(player).forEach(packetBlock -> states.add(packetBlock.getBlockState(player))); - player.sendBlockChanges(states); + getBlocksByViewer(player).forEach(DataHolder::sendUpdates); return; } - getBlocksByViewerWithMeta(player, metaKey).forEach(packetBlock -> states.add(packetBlock.getBlockState(player))); - player.sendBlockChanges(states); + getBlocksByViewerWithMeta(player, metaKey).forEach(DataHolder::sendUpdates); } } diff --git a/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java b/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java index ccf2535..a49f2f2 100644 --- a/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java +++ b/src/main/java/net/bitbylogic/packetblocks/data/DataHandler.java @@ -3,23 +3,25 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; -import lombok.Setter; import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; import net.bitbylogic.packetblocks.viewer.ViewerHolder; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.Nullable; -import java.util.Iterator; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.function.Consumer; +import java.util.function.Function; public class DataHandler> { + private final List boundingBoxes = new ArrayList<>(); + @Getter(AccessLevel.NONE) private final ViewerHolder viewerHandler; private final Consumer updateConsumer; + private final Function> boundingBoxProvider; public T data; @@ -28,11 +30,19 @@ public class DataHandler> { private boolean addViewerOnJoin; private boolean globalBreakAnimation; - public DataHandler(ViewerHolder viewerHandler, Consumer updateConsumer, T data, int breakSpeed) { + public DataHandler(ViewerHolder viewerHandler, Consumer updateConsumer, Function> boundingBoxProvider, T data, int breakSpeed) { this.viewerHandler = viewerHandler; this.updateConsumer = updateConsumer; this.data = data; this.breakSpeed = breakSpeed; + + this.boundingBoxProvider = t -> { + boundingBoxes.clear(); + + boundingBoxes.addAll(boundingBoxProvider.apply(t)); + + return boundingBoxes; + }; } /** @@ -46,6 +56,8 @@ protected void setDataForAll(@NonNull T data) { } sendUpdates(); + + boundingBoxProvider.apply(data); } /** @@ -62,6 +74,8 @@ protected void setDataSupplierForAll(@NonNull T data) { } sendUpdates(); + + boundingBoxProvider.apply(data); } /** @@ -142,10 +156,28 @@ protected void sendUpdates() { } } + protected List getBoundingBoxes() { + return boundingBoxes; + } + protected T getData() { return data; } + protected T getData(@Nullable Player player) { + if(player == null) { + return data; + } + + Optional optionalViewer = viewerHandler.getViewer(player); + + if(optionalViewer.isEmpty()) { + return data; + } + + return optionalViewer.get().getSuppliedData(); + } + protected int getBreakSpeed() { return breakSpeed; } diff --git a/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java b/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java index ceb2afa..13848e5 100644 --- a/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java +++ b/src/main/java/net/bitbylogic/packetblocks/data/DataHolder.java @@ -3,8 +3,11 @@ import lombok.NonNull; import net.bitbylogic.packetblocks.viewer.PacketBlockViewer; import org.bukkit.entity.Player; +import org.bukkit.util.BoundingBox; import org.jetbrains.annotations.Nullable; +import java.util.List; + public interface DataHolder> { @NonNull DataHandler getDataHandler(); @@ -82,10 +85,18 @@ default void sendUpdates() { getDataHandler().sendUpdates(); } + default List getBoundingBoxes() { + return getDataHandler().getBoundingBoxes(); + } + default T getData() { return getDataHandler().getData(); } + default T getData(@Nullable Player player) { + return getDataHandler().getData(player); + } + default boolean isAddViewerOnJoin() { return getDataHandler().isAddViewerOnJoin(); } diff --git a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockBreakEvent.java b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockBreakEvent.java index cf2f7f8..03e7c1b 100644 --- a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockBreakEvent.java +++ b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockBreakEvent.java @@ -2,7 +2,7 @@ import lombok.Getter; import lombok.Setter; -import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -12,10 +12,10 @@ import org.jetbrains.annotations.NotNull; /** - * Called when a {@link PacketBlock} is broken by a player. + * Called when a {@link PacketBlockHolder} is broken by a player. * *

This event is fired by the PacketBlocks plugin whenever a - * player breaks a {@link PacketBlock} instance in the world. + * player breaks a {@link PacketBlockHolder} instance in the world. * *

Handlers may choose to cancel this event to prevent the block * from being broken. @@ -29,7 +29,7 @@ public class PacketBlockBreakEvent extends Event implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); private final Player player; - private final PacketBlock block; + private final PacketBlockHolder block; private final Location location; private final ItemStack tool; @@ -41,7 +41,7 @@ public class PacketBlockBreakEvent extends Event implements Cancellable { private boolean cancelled; - public PacketBlockBreakEvent(Player player, PacketBlock block, Location location, ItemStack tool) { + public PacketBlockBreakEvent(Player player, PacketBlockHolder block, Location location, ItemStack tool) { this.player = player; this.block = block; this.location = location; diff --git a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockInteractEvent.java b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockInteractEvent.java index 91e877e..bbed754 100644 --- a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockInteractEvent.java +++ b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockInteractEvent.java @@ -2,7 +2,8 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; +import org.bukkit.Location; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -12,10 +13,10 @@ import org.jetbrains.annotations.NotNull; /** - * Called when a player interacts with a {@link PacketBlock}. + * Called when a player interacts with a {@link PacketBlockHolder}. * *

This event is fired by the PacketBlocks plugin whenever a - * player right or left-clicks a {@link PacketBlock} instance in the world. + * player right or left-clicks a {@link PacketBlockHolder} instance in the world. * *

Handlers may choose to listen to this event to add custom * interaction behavior to packet blocks. @@ -33,7 +34,8 @@ public class PacketBlockInteractEvent extends Event { private final Action action; private final EquipmentSlot hand; private final BlockFace blockFace; - private final PacketBlock block; + private final PacketBlockHolder block; + private final Location location; public static HandlerList getHandlerList() { return HANDLERS; diff --git a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockStartBreakEvent.java b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockStartBreakEvent.java index ebb9fc7..ac8fd9a 100644 --- a/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockStartBreakEvent.java +++ b/src/main/java/net/bitbylogic/packetblocks/event/PacketBlockStartBreakEvent.java @@ -1,7 +1,7 @@ package net.bitbylogic.packetblocks.event; import lombok.Getter; -import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull; /** - * Called when a player starts breaking a {@link PacketBlock}. + * Called when a player starts breaking a {@link PacketBlockHolder}. * *

This event is triggered as soon as the player begins * interacting with the block (e.g. starts mining it), @@ -25,12 +25,12 @@ public class PacketBlockStartBreakEvent extends Event implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); private final Player player; - private final PacketBlock block; + private final PacketBlockHolder block; private final Location location; private boolean cancelled; - public PacketBlockStartBreakEvent(Player player, PacketBlock block, Location location) { + public PacketBlockStartBreakEvent(Player player, PacketBlockHolder block, Location location) { this.player = player; this.block = block; this.location = location; diff --git a/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java b/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java index 7c78378..c4e5395 100644 --- a/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java +++ b/src/main/java/net/bitbylogic/packetblocks/group/PacketBlockGroup.java @@ -5,42 +5,54 @@ import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.data.DataHandler; import net.bitbylogic.packetblocks.metadata.MetadataHandler; +import net.bitbylogic.packetblocks.util.BoundingBoxes; import net.bitbylogic.packetblocks.viewer.ViewerHandler; import net.bitbylogic.packetblocks.viewer.impl.GroupPacketBlockViewer; +import net.bitbylogic.utils.location.ChunkPosition; import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.util.BoundingBox; +import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @Getter public class PacketBlockGroup implements PacketBlockHolder, GroupPacketBlockViewer> { + private final List worldNames = new ArrayList<>(); + private final Map> chunkPositions; private final Map cachedLocations; - private final List boundingBoxes; - private final DataHandler, GroupPacketBlockViewer> dataHandler; private final ViewerHandler, GroupPacketBlockViewer> viewerHandler; private final MetadataHandler metadataHandler; + public PacketBlockGroup(@NonNull Map blockLocations) { + this(blockLocations, -1); + } + public PacketBlockGroup(@NonNull Map blockLocations, int breakSpeed) { int size = blockLocations.size(); + this.chunkPositions = new HashMap<>(size); this.cachedLocations = new HashMap<>(size); - this.boundingBoxes = new ArrayList<>(size); Map positions = new HashMap<>(size); for (Map.Entry entry : blockLocations.entrySet()) { - Location location = entry.getKey(); + Location location = entry.getKey().toBlockLocation(); WorldPosition worldPosition = WorldPosition.ofBlock(location); + ChunkPosition chunkPosition = worldPosition.toChunkPosition(); + + if (!worldNames.contains(worldPosition.worldName())) { + worldNames.add(worldPosition.worldName()); + } + + chunkPositions.computeIfAbsent(chunkPosition, k -> new ArrayList<>()).add(worldPosition); positions.put(worldPosition, entry.getValue()); cachedLocations.put(worldPosition, location); @@ -49,11 +61,28 @@ public PacketBlockGroup(@NonNull Map blockLocations, int br this.viewerHandler = new ViewerHandler<>( player -> positions, this::sendUpdate, - player -> {}, + player -> { + List states = new ArrayList<>(); + + for (Map.Entry entry : getData().entrySet()) { + Location location = cachedLocations.get(entry.getKey()); + states.add(location.getBlock().getState()); + } + + player.sendBlockChanges(states); + }, () -> new GroupPacketBlockViewer(positions, () -> positions, breakSpeed) ); - this.dataHandler = new DataHandler<>(this, this::sendUpdate, positions, breakSpeed); + this.dataHandler = new DataHandler<>(this, this::sendUpdate, map -> { + List boundingBoxes = new ArrayList<>(); + + for (Map.Entry entry : map.entrySet()) { + boundingBoxes.addAll(BoundingBoxes.getBoxesAt(entry.getValue(), cachedLocations.get(entry.getKey()))); + } + + return boundingBoxes; + }, positions, breakSpeed); this.metadataHandler = new MetadataHandler(); } @@ -82,13 +111,38 @@ public List getBlockStates(@NonNull Player player) { return states; } + public Optional getDataAt(@Nullable Player player, @NonNull Location location) { + if (player == null) { + return Optional.ofNullable(getData().get(WorldPosition.ofBlock(location))); + } + + Optional optionalViewer = getViewer(player); + + if(optionalViewer.isPresent()) { + return Optional.ofNullable(optionalViewer.get().getData().get(WorldPosition.ofBlock(location))); + } + + return Optional.ofNullable(getData().get(WorldPosition.ofBlock(location))); + } + /** * Sends a block update to the specified player at the current location. * * @param player the player to whom the block update will be sent */ + @Override public void sendUpdate(@NonNull Player player) { player.sendBlockChanges(getBlockStates(player)); } + @Override + public boolean existsIn(@NonNull World world) { + return worldNames.contains(world.getName()); + } + + @Override + public boolean existsAt(@NonNull Location location) { + return getData().containsKey(WorldPosition.ofBlock(location)); + } + } diff --git a/src/main/java/net/bitbylogic/packetblocks/listener/PacketBlockListener.java b/src/main/java/net/bitbylogic/packetblocks/listener/PacketBlockListener.java index 364a679..ea5f180 100644 --- a/src/main/java/net/bitbylogic/packetblocks/listener/PacketBlockListener.java +++ b/src/main/java/net/bitbylogic/packetblocks/listener/PacketBlockListener.java @@ -2,10 +2,13 @@ import lombok.RequiredArgsConstructor; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.block.PacketBlockManager; import net.bitbylogic.packetblocks.event.PacketBlockInteractEvent; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; import net.bitbylogic.packetblocks.util.PacketBlockUtil; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.attribute.Attribute; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; @@ -43,10 +46,19 @@ public void onJoin(PlayerJoinEvent event) { Set states = new HashSet<>(); manager.getBlocks(player.getWorld()).stream() - .filter(PacketBlock::isAddViewerOnJoin) + .filter(PacketBlockHolder::isAddViewerOnJoin) .forEach(packetBlock -> { - packetBlock.attemptAddViewer(player, false) - .ifPresent(pd -> states.add(packetBlock.getBlockState(player))); + if (packetBlock instanceof PacketBlock singleBlock) { + singleBlock.attemptAddViewer(player, false) + .ifPresent(pd -> states.add(((PacketBlock) packetBlock).getBlockState(player))); + return; + } + + if (!(packetBlock instanceof PacketBlockGroup group)) { + return; + } + + group.attemptAddViewer(player, false).ifPresent(pd -> states.addAll(group.getBlockStates(player))); }); player.sendBlockChanges(states); @@ -57,7 +69,7 @@ public void onQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); manager.getBlocks(player.getWorld()).stream() - .filter(PacketBlock::isAddViewerOnJoin) + .filter(PacketBlockHolder::isAddViewerOnJoin) .forEach(packetBlock -> packetBlock.removeViewer(player)); } @@ -72,8 +84,10 @@ public void onInteract(PlayerInteractEvent event) { return; } + Location location = result.getHitBlock().getLocation(); + PacketBlockInteractEvent interactEvent = new PacketBlockInteractEvent(player, event.getAction(), event.getHand(), - result.getHitBlockFace(), manager.getBlock(result.getHitBlock().getLocation()).get()); + result.getHitBlockFace(), manager.getBlock(location).get(), location); Bukkit.getPluginManager().callEvent(interactEvent); } diff --git a/src/main/java/net/bitbylogic/packetblocks/task/BlockAnimationContext.java b/src/main/java/net/bitbylogic/packetblocks/task/BlockAnimationContext.java index 77b08b3..91ff8e9 100644 --- a/src/main/java/net/bitbylogic/packetblocks/task/BlockAnimationContext.java +++ b/src/main/java/net/bitbylogic/packetblocks/task/BlockAnimationContext.java @@ -4,17 +4,18 @@ import lombok.NonNull; import lombok.Setter; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; @Getter @Setter public class BlockAnimationContext { - private final PacketBlock block; + private final PacketBlockHolder block; private int stage = -1; private int ticksTaken = 0; - public BlockAnimationContext(@NonNull PacketBlock block) { + public BlockAnimationContext(@NonNull PacketBlockHolder block) { this.block = block; } diff --git a/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java b/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java index 1ff4a61..17536d8 100644 --- a/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java +++ b/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java @@ -7,6 +7,9 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityEffect; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRemoveEntityEffect; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; +import net.bitbylogic.utils.location.WorldPosition; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -26,7 +29,7 @@ public void run() { for (Map.Entry entry : blockMap.entrySet()) { Player player = entry.getKey(); BlockAnimationContext context = entry.getValue(); - PacketBlock packetBlock = context.getBlock(); + PacketBlockHolder packetBlock = context.getBlock(); int breakSpeed = packetBlock.getBreakSpeed(player); int ticksTaken = context.getTicksTaken(); @@ -56,7 +59,7 @@ public void run() { } } - public void addEntry(Player player, PacketBlock block) { + public void addEntry(Player player, PacketBlockHolder block) { // Apply Mining Fatigue effect via PacketEvents WrapperPlayServerEntityEffect effectPacket = new WrapperPlayServerEntityEffect(player.getEntityId(), PotionTypes.MINING_FATIGUE, 127, Integer.MAX_VALUE, (byte) 1); @@ -71,7 +74,7 @@ public void removeEntry(Player player) { if (context == null) return; - PacketBlock block = context.getBlock(); + PacketBlockHolder block = context.getBlock(); // Remove break animation sendFinishBreak(player, block); @@ -82,15 +85,30 @@ public void removeEntry(Player player) { PacketEvents.getAPI().getPlayerManager().sendPacket(player, removeEffect); } - private void sendAnimation(Player player, PacketBlock block, int stage) { - Location blockLocation = block.getLocation(); - Vector3i position = new Vector3i(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()); + private void sendAnimation(Player player, PacketBlockHolder block, int stage) { + if(block instanceof PacketBlock singleBlock) { + Location blockLocation = singleBlock.getLocation(); + Vector3i position = new Vector3i(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()); - WrapperPlayServerBlockBreakAnimation animation = new WrapperPlayServerBlockBreakAnimation(player.getEntityId(), position, (byte) stage); - PacketEvents.getAPI().getPlayerManager().sendPacket(player, animation); + WrapperPlayServerBlockBreakAnimation animation = new WrapperPlayServerBlockBreakAnimation(player.getEntityId(), position, (byte) stage); + PacketEvents.getAPI().getPlayerManager().sendPacket(player, animation); + return; + } + + if(!(block instanceof PacketBlockGroup group)) { + return; + } + + for (WorldPosition worldPosition : group.getData().keySet()) { + Location blockLocation = group.getCachedLocations().get(worldPosition); + Vector3i position = new Vector3i(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()); + + WrapperPlayServerBlockBreakAnimation animation = new WrapperPlayServerBlockBreakAnimation(player.getEntityId(), position, (byte) stage); + PacketEvents.getAPI().getPlayerManager().sendPacket(player, animation); + } } - private void sendAnimationToAllViewers(PacketBlock block, int stage) { + private void sendAnimationToAllViewers(PacketBlockHolder block, int stage) { Iterator it = block.getViewers().keySet().iterator(); while (it.hasNext()) { @@ -104,8 +122,7 @@ private void sendAnimationToAllViewers(PacketBlock block, int stage) { } } - private void sendFinishBreak(Player player, PacketBlock block) { - // Send break animation stage -1 to indicate finished + private void sendFinishBreak(Player player, PacketBlockHolder block) { sendAnimation(player, block, -1); if (block.isGlobalBreakAnimation()) { diff --git a/src/main/java/net/bitbylogic/packetblocks/util/PacketBlockUtil.java b/src/main/java/net/bitbylogic/packetblocks/util/PacketBlockUtil.java index 41c98c3..b36de80 100644 --- a/src/main/java/net/bitbylogic/packetblocks/util/PacketBlockUtil.java +++ b/src/main/java/net/bitbylogic/packetblocks/util/PacketBlockUtil.java @@ -3,7 +3,9 @@ import lombok.NonNull; import net.bitbylogic.packetblocks.PacketBlocks; import net.bitbylogic.packetblocks.block.PacketBlock; +import net.bitbylogic.packetblocks.block.PacketBlockHolder; import net.bitbylogic.packetblocks.block.PacketBlockManager; +import net.bitbylogic.packetblocks.group.PacketBlockGroup; import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; @@ -13,9 +15,42 @@ import org.bukkit.entity.Player; import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; public class PacketBlockUtil { + public static BlockData getBlockData(@Nullable Player player, @NonNull Location location) { + if(location.getWorld() == null) { + return Material.AIR.createBlockData(); + } + + Optional> optionalHolder = PacketBlocks.getInstance().getBlockManager().getBlock(location); + + if(optionalHolder.isEmpty()) { + return location.getBlock().getBlockData(); + } + + PacketBlockHolder packetBlockHolder = optionalHolder.get(); + + if (packetBlockHolder instanceof PacketBlock packetBlock) { + return packetBlock.getData(); + } + + if(packetBlockHolder instanceof PacketBlockGroup group) { + Optional optionalData = group.getDataAt(player, location); + + if(optionalData.isEmpty()) { + return location.getBlock().getBlockData(); + } + + return optionalData.get(); + } + + return location.getBlock().getBlockData(); + } + /** * Retrieves the material type of the block at a specific location for a given player. * This method first checks for any custom packet-based block at the provided location @@ -26,13 +61,34 @@ public class PacketBlockUtil { * @param location the location of the block to check; must not be null * @return the material representing the block type at the specified location */ - public static Material getBlockType(@NonNull Player player, @NonNull Location location) { + public static Material getBlockType(@Nullable Player player, @NonNull Location location) { if(location.getWorld() == null) { return Material.AIR; } - return PacketBlocks.getInstance().getBlockManager().getBlock(location) - .map(packetBlock -> packetBlock.getBlockState(player).getBlockData().getMaterial()).orElse(location.getBlock().getType()); + Optional> optionalHolder = PacketBlocks.getInstance().getBlockManager().getBlock(location); + + if(optionalHolder.isEmpty()) { + return location.getBlock().getType(); + } + + PacketBlockHolder packetBlockHolder = optionalHolder.get(); + + if (packetBlockHolder instanceof PacketBlock packetBlock) { + return packetBlock.getData().getMaterial(); + } + + if(packetBlockHolder instanceof PacketBlockGroup group) { + Optional optionalData = group.getDataAt(player, location); + + if(optionalData.isEmpty()) { + return location.getBlock().getType(); + } + + return optionalData.get().getMaterial(); + } + + return location.getBlock().getType(); } /** @@ -59,17 +115,43 @@ public static RayTraceResult rayTrace(Player player, double range) { for (double traveled = 0; traveled <= range; traveled += step) { current.add(direction.clone().multiply(step)); Block block = world.getBlockAt(current.toLocation(world)); - PacketBlock packetBlock = blockManager.getBlock(block.getLocation()).orElse(null); + PacketBlockHolder packetBlock = blockManager.getBlock(block.getLocation()).orElse(null); - if (packetBlock != null) { - BlockData blockData = packetBlock.getBlockState(player).getBlockData(); + if(packetBlock == null) { + continue; + } + + if (packetBlock instanceof PacketBlock singleBlock) { + BlockData blockData = singleBlock.getData(player); RayTraceResult boxResult = BoundingBoxes.rayTraceAt(block, blockData, eye.toVector(), direction, range); - if (boxResult != null) { - return new RayTraceResult(boxResult.getHitPosition(), block, boxResult.getHitBlockFace()); + if(boxResult == null) { + continue; } + + return new RayTraceResult(boxResult.getHitPosition(), block, boxResult.getHitBlockFace()); } + + if (!(packetBlock instanceof PacketBlockGroup group)) { + continue; + } + + Optional optionalBlockData = group.getDataAt(player, block.getLocation()); + + if (optionalBlockData.isEmpty()) { + continue; + } + + BlockData blockData = optionalBlockData.get(); + + RayTraceResult boxResult = BoundingBoxes.rayTraceAt(block, blockData, eye.toVector(), direction, range); + + if(boxResult == null) { + continue; + } + + return new RayTraceResult(boxResult.getHitPosition(), block, boxResult.getHitBlockFace()); } return vanillaResult; From 9aca9f27a53ecb914d67051c4c4605cde61f69f3 Mon Sep 17 00:00:00 2001 From: Justin <33465177+BitByLogics@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:09:11 -0500 Subject: [PATCH 3/3] Remove unneeded comments --- .../packetblocks/task/PacketBlockAnimationTask.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java b/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java index 17536d8..e3de96d 100644 --- a/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java +++ b/src/main/java/net/bitbylogic/packetblocks/task/PacketBlockAnimationTask.java @@ -48,7 +48,6 @@ public void run() { continue; } - // Send block break animation if (packetBlock.isGlobalBreakAnimation()) { sendAnimationToAllViewers(packetBlock, stage); } else { @@ -60,7 +59,6 @@ public void run() { } public void addEntry(Player player, PacketBlockHolder block) { - // Apply Mining Fatigue effect via PacketEvents WrapperPlayServerEntityEffect effectPacket = new WrapperPlayServerEntityEffect(player.getEntityId(), PotionTypes.MINING_FATIGUE, 127, Integer.MAX_VALUE, (byte) 1); @@ -76,10 +74,8 @@ public void removeEntry(Player player) { PacketBlockHolder block = context.getBlock(); - // Remove break animation sendFinishBreak(player, block); - // Remove Mining Fatigue WrapperPlayServerRemoveEntityEffect removeEffect = new WrapperPlayServerRemoveEntityEffect(player.getEntityId(), PotionTypes.MINING_FATIGUE); PacketEvents.getAPI().getPlayerManager().sendPacket(player, removeEffect);