Skip to content

Commit de07bec

Browse files
committed
BlockChangeManager optimizations
1 parent 9ed2d3f commit de07bec

10 files changed

Lines changed: 100 additions & 18 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group = 'codes.kooper'
7-
version = '1.1.5-beta'
7+
version = '1.1.6-beta'
88

99
dependencies {
1010
implementation 'org.projectlombok:lombok:1.18.28'

src/main/java/codes/kooper/blockify/Blockify.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import codes.kooper.blockify.protocol.PlayerInfoAdapter;
1111
import codes.kooper.blockify.utils.MiningUtils;
1212
import com.github.retrooper.packetevents.PacketEvents;
13+
import com.github.retrooper.packetevents.manager.server.ServerVersion;
1314
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
1415
import lombok.Getter;
1516
import org.bukkit.plugin.java.JavaPlugin;
@@ -19,6 +20,7 @@ public final class Blockify extends JavaPlugin {
1920
private StageManager stageManager;
2021
private BlockChangeManager blockChangeManager;
2122
private MiningUtils miningUtils;
23+
private ServerVersion serverVersion;
2224

2325
@Override
2426
public void onLoad() {
@@ -32,6 +34,7 @@ public void onLoad() {
3234
@Override
3335
public void onEnable() {
3436
new Metrics(this, 21782);
37+
serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
3538
getLogger().info("Blockify has been enabled!");
3639

3740
stageManager = new StageManager();

src/main/java/codes/kooper/blockify/managers/BlockChangeManager.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121

2222
import java.util.*;
2323
import java.util.concurrent.ConcurrentHashMap;
24-
import java.util.concurrent.atomic.AtomicInteger;
24+
import java.util.concurrent.ExecutorService;
25+
import java.util.concurrent.Executors;
2526

2627
@Getter
2728
public class BlockChangeManager {
@@ -147,15 +148,13 @@ public void sendBlockChanges(Stage stage, Audience audience, ConcurrentHashMap<B
147148
}
148149

149150
// Send multiple block changes to the players
151+
ExecutorService executorService = Executors.newSingleThreadExecutor();
150152
for (Player onlinePlayer : audience.getOnlinePlayers()) {
151153
Location playerLocation = onlinePlayer.getLocation();
152154
if (playerLocation.getWorld() != stage.getWorld()) continue;
153155

154-
// The chunk index is used to keep track of the current chunk being sent
155-
AtomicInteger chunkIndex = new AtomicInteger(0);
156156
// Create an array of chunks to send from the block changes map
157-
List<BlockifyChunk> chunksToSend = new ArrayList<>(List.of(blockChanges.keySet().toArray(new BlockifyChunk[0])));
158-
chunksToSend.sort((chunk1, chunk2) -> {
157+
Comparator<BlockifyChunk> comparator = (chunk1, chunk2) -> {
159158
// Get distance from chunks to player
160159
int x = playerLocation.getBlockX() / 16;
161160
int z = playerLocation.getBlockZ() / 16;
@@ -170,14 +169,17 @@ public void sendBlockChanges(Stage stage, Audience audience, ConcurrentHashMap<B
170169

171170
// Compare distances and return accordingly
172171
return Integer.compare(distanceSquared1, distanceSquared2);
173-
});
172+
};
173+
Queue<BlockifyChunk> chunksToSend = new PriorityQueue<>(comparator);
174+
chunksToSend.addAll(blockChanges.keySet());
174175

175176
// Create a task to send a chunk to the player every tick
176177
blockChangeTasks.put(onlinePlayer, Bukkit.getScheduler().runTaskTimer(Blockify.getInstance(), () -> {
177178
// Check if player is online, if not, cancel the task
178179
if (!onlinePlayer.isOnline()) {
179180
blockChangeTasks.computeIfPresent(onlinePlayer, (key, task) -> {
180181
task.cancel();
182+
executorService.shutdown();
181183
return null;
182184
});
183185
return;
@@ -186,24 +188,24 @@ public void sendBlockChanges(Stage stage, Audience audience, ConcurrentHashMap<B
186188
// Loop through chunks per tick
187189
for (int i = 0; i < stage.getChunksPerTick(); i++) {
188190
// If the chunk index is greater than the chunks to send length
189-
if (chunkIndex.get() >= chunksToSend.size()) {
191+
if (chunksToSend.isEmpty()) {
190192
// Safely cancel the task and remove it from the map
191193
blockChangeTasks.computeIfPresent(onlinePlayer, (key, task) -> {
192194
task.cancel();
195+
executorService.shutdown();
193196
return null; // Remove the task
194197
});
195198
return;
196199
}
197200

198201
// Get the chunk from the chunks to send array
199-
BlockifyChunk chunk = chunksToSend.get(chunkIndex.get());
200-
chunkIndex.getAndIncrement();
202+
BlockifyChunk chunk = chunksToSend.poll();
201203

202204
// Check if the chunk is loaded; if not, return
203205
if (!stage.getWorld().isChunkLoaded(chunk.x(), chunk.z())) return;
204206

205207
// Send the chunk packet to the player
206-
Bukkit.getScheduler().runTaskAsynchronously(Blockify.getInstance(), () -> sendChunkPacket(stage, onlinePlayer, chunk, blockChanges));
208+
executorService.submit(() -> sendChunkPacket(stage, onlinePlayer, chunk, blockChanges));
207209
}
208210
}, 0L, 1L));
209211
}
@@ -220,7 +222,7 @@ public void sendBlockChanges(Stage stage, Audience audience, ConcurrentHashMap<B
220222
*/
221223
public void sendChunkPacket(Stage stage, Player player, BlockifyChunk chunk, ConcurrentHashMap<BlockifyChunk, ConcurrentHashMap<BlockifyPosition, BlockData>> blockChanges) {
222224
// Get the user from PacketEvents API
223-
User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
225+
final User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
224226

225227
// Loop through the chunks y positions
226228
for (int chunkY = stage.getMinPosition().getY() >> 4; chunkY <= stage.getMaxPosition().getY() >> 4; chunkY++) {
@@ -250,7 +252,8 @@ public void sendChunkPacket(Stage stage, Player player, BlockifyChunk chunk, Con
250252
// Send the packet to the player
251253
WrapperPlayServerMultiBlockChange.EncodedBlock[] encodedBlocksArray = encodedBlocks.toArray(new WrapperPlayServerMultiBlockChange.EncodedBlock[0]);
252254
WrapperPlayServerMultiBlockChange wrapper = new WrapperPlayServerMultiBlockChange(new Vector3i(chunk.x(), chunkY, chunk.z()), true, encodedBlocksArray);
253-
Bukkit.getScheduler().runTask(Blockify.getInstance(), () -> user.sendPacket(wrapper));
255+
if (user == null || !player.isOnline()) return;
256+
user.sendPacket(wrapper);
254257
}
255258
}
256259

src/main/java/codes/kooper/blockify/models/View.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class View {
1515
private final ConcurrentHashMap<BlockifyChunk, ConcurrentHashMap<BlockifyPosition, BlockData>> blocks;
1616
private final Stage stage;
1717
private final String name;
18-
private boolean breakable;
18+
private boolean breakable, placeable;
1919
private Pattern pattern;
2020

2121
/**

src/main/java/codes/kooper/blockify/protocol/BlockDigAdapter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
1616
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
1717
import org.bukkit.Bukkit;
18+
import org.bukkit.Material;
1819
import org.bukkit.block.data.BlockData;
1920
import org.bukkit.entity.Player;
2021

@@ -51,7 +52,7 @@ public void onPacketPlayReceive(PacketPlayReceiveEvent event) {
5152
Bukkit.getScheduler().runTask(Blockify.getInstance(), () -> new BlockifyInteractEvent(player, position.toPosition(), blockData, view, view.getStage()).callEvent());
5253

5354
// Check if block is breakable, if not, send block change packet to cancel the break
54-
if (!view.isBreakable()) {
55+
if (!view.isBreakable() || blockData.getMaterial() == Material.BEDROCK) {
5556
WrapperPlayServerBlockChange wrapperPlayServerBlockChange = new WrapperPlayServerBlockChange(new Vector3i(position.getX(), position.getY(), position.getZ()), SpigotConversionUtil.fromBukkitBlockData(blockData).getGlobalId());
5657
PacketEvents.getAPI().getPlayerManager().sendPacket(player, wrapperPlayServerBlockChange);
5758
return;

src/main/java/codes/kooper/blockify/protocol/BlockPlaceAdapter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import codes.kooper.blockify.types.BlockifyPosition;
88
import com.github.retrooper.packetevents.event.SimplePacketListenerAbstract;
99
import com.github.retrooper.packetevents.event.simple.PacketPlayReceiveEvent;
10+
import com.github.retrooper.packetevents.event.simple.PacketPlaySendEvent;
1011
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
1112
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
13+
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
1214
import org.bukkit.Bukkit;
1315
import org.bukkit.entity.Player;
1416

@@ -45,4 +47,29 @@ public void onPacketPlayReceive(PacketPlayReceiveEvent event) {
4547
}
4648
}
4749

50+
@Override
51+
public void onPacketPlaySend(PacketPlaySendEvent event) {
52+
if (event.getPacketType() == PacketType.Play.Server.BLOCK_CHANGE) {
53+
WrapperPlayServerBlockChange wrapper = new WrapperPlayServerBlockChange(event);
54+
Player player = (Player) event.getPlayer();
55+
56+
// Get the stages the player is in. If the player is not in any stages, return.
57+
List<Stage> stages = Blockify.getInstance().getStageManager().getStages(player);
58+
if (stages == null || stages.isEmpty()) {
59+
return;
60+
}
61+
62+
BlockifyPosition position = new BlockifyPosition(wrapper.getBlockPosition().getX(), wrapper.getBlockPosition().getY(), wrapper.getBlockPosition().getZ());
63+
for (Stage stage : stages) {
64+
for (View view : stage.getViews()) {
65+
if (view.hasBlock(position)) {
66+
if (wrapper.getBlockState().getType().getName().equalsIgnoreCase(view.getBlock(position).getMaterial().name())) continue;
67+
event.setCancelled(true);
68+
return;
69+
}
70+
}
71+
}
72+
}
73+
}
74+
4875
}

src/main/java/codes/kooper/blockify/types/BlockifyPosition.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public static BlockifyPosition fromLocation(Location location) {
4040
return new BlockifyPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ());
4141
}
4242

43+
/**
44+
* Create a new BlockifyPosition
45+
*
46+
* @param vector The vector to create the BlockifyPosition from
47+
*/
48+
public static BlockifyPosition fromVector(Vector vector) {
49+
return new BlockifyPosition(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ());
50+
}
51+
4352
/**
4453
* Creates new BlockifyPositions
4554
*

src/main/java/codes/kooper/blockify/utils/BlockUtils.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package codes.kooper.blockify.utils;
22

33
import codes.kooper.blockify.types.BlockifyPosition;
4+
import org.bukkit.Location;
45
import org.bukkit.block.data.Ageable;
56
import org.bukkit.block.data.BlockData;
67

8+
import java.util.ArrayList;
79
import java.util.HashSet;
10+
import java.util.List;
811
import java.util.Set;
912

1013
public class BlockUtils {
@@ -35,6 +38,32 @@ public static Set<BlockifyPosition> getBlocksBetween(BlockifyPosition pos1, Bloc
3538
return positions;
3639
}
3740

41+
/**
42+
* Get all the locations between two locations.
43+
* Call this method asynchronously if you are going to be getting a large amount of locations.
44+
*
45+
* @param loc1 The first location.
46+
* @param loc2 The second location.
47+
* @return A list of all the locations between the two locations.
48+
*/
49+
public static List<Location> getLocationsBetween(Location loc1, Location loc2) {
50+
List<Location> locations = new ArrayList<>();
51+
int minX = Math.min(loc1.getBlockX(), loc2.getBlockX());
52+
int minY = Math.min(loc1.getBlockY(), loc2.getBlockY());
53+
int minZ = Math.min(loc1.getBlockZ(), loc2.getBlockZ());
54+
int maxX = Math.max(loc1.getBlockX(), loc2.getBlockX());
55+
int maxY = Math.max(loc1.getBlockY(), loc2.getBlockY());
56+
int maxZ = Math.max(loc1.getBlockZ(), loc2.getBlockZ());
57+
for (int x = minX; x <= maxX; x++) {
58+
for (int y = minY; y <= maxY; y++) {
59+
for (int z = minZ; z <= maxZ; z++) {
60+
locations.add(new Location(loc1.getWorld(), x, y, z));
61+
}
62+
}
63+
}
64+
return locations;
65+
}
66+
3867
/**
3968
* Set the age of a block.
4069
*

src/main/java/codes/kooper/blockify/utils/MiningUtils.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import codes.kooper.blockify.types.BlockifyBlockStage;
77
import codes.kooper.blockify.types.BlockifyPosition;
88
import com.github.retrooper.packetevents.PacketEvents;
9+
import com.github.retrooper.packetevents.manager.server.ServerVersion;
910
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
1011
import com.github.retrooper.packetevents.util.Vector3i;
1112
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockBreakAnimation;
@@ -15,6 +16,7 @@
1516
import org.bukkit.Material;
1617
import org.bukkit.Particle;
1718
import org.bukkit.block.data.BlockData;
19+
import org.bukkit.enchantments.Enchantment;
1820
import org.bukkit.entity.Player;
1921
import org.bukkit.inventory.ItemStack;
2022
import org.bukkit.potion.PotionEffect;
@@ -119,7 +121,11 @@ public void handleNormalDigging(Player player, View view, DiggingAction actionTy
119121
* @return boolean
120122
*/
121123
public boolean canInstantBreak(Player player, BlockData blockData) {
122-
return blockData.getDestroySpeed(player.getInventory().getItemInMainHand(), true) >= blockData.getMaterial().getHardness() * 30 || player.getGameMode() == GameMode.CREATIVE;
124+
if (Blockify.getInstance().getServerVersion().isOlderThan(ServerVersion.V_1_20)) {
125+
return calculateMiningTimeInMilliseconds(blockData, player) <= 50;
126+
} else {
127+
return blockData.getDestroySpeed(player.getInventory().getItemInMainHand(), true) >= blockData.getMaterial().getHardness() * 30 || player.getGameMode() == GameMode.CREATIVE;
128+
}
123129
}
124130

125131
/**
@@ -229,13 +235,17 @@ private double calculateMiningTimeInMilliseconds(BlockData block, Player player)
229235
double speedMultiplier = 1.0;
230236
// Check if player is using the preferred tool
231237
boolean isPreferredTool = block.isPreferredTool(player.getInventory().getItemInMainHand());
238+
// Efficiency level
239+
int efficiencyLevel = player.getInventory().getItemInMainHand().getEnchantmentLevel(Enchantment.DIG_SPEED);
232240
// Check if player can harvest the block
233241
boolean canHarvest = isPreferredTool && block.requiresCorrectToolForDrops();
234242
// If player is using the preferred tool, get the speed multiplier, otherwise, set it to 1.0
235243
if (isPreferredTool) {
236244
speedMultiplier = getToolSpeed(player.getInventory().getItemInMainHand(), block);
237245
if (!canHarvest) {
238246
speedMultiplier = 1.0;
247+
} else if (efficiencyLevel > 0) {
248+
speedMultiplier += Math.pow(efficiencyLevel, 2) + 1;
239249
}
240250
}
241251

src/main/resources/plugin.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Blockify
2-
version: '1.1.5-beta'
2+
version: '1.1.6-beta'
33
main: codes.kooper.blockify.Blockify
4-
api-version: '1.20'
4+
api-version: '1.19'
55
depend:
66
- packetevents

0 commit comments

Comments
 (0)