2424import org .bukkit .util .Vector ;
2525import org .bukkit .event .block .BlockPhysicsEvent ;
2626import org .bukkit .event .player .PlayerTeleportEvent ;
27+ import org .bukkit .event .block .BlockExplodeEvent ;
28+ import org .bukkit .event .block .BlockFromToEvent ;
2729import org .bukkit .event .player .PlayerChangedWorldEvent ;
2830
2931import java .util .concurrent .ExecutorService ;
@@ -67,7 +69,7 @@ public class TuffX extends JavaPlugin implements Listener, PluginMessageListener
6769 private final ThreadLocal <short []> threadLocalBlockArray = ThreadLocal .withInitial (() -> new short [4096 ]);
6870 private final ThreadLocal <byte []> threadLocalLightArray = ThreadLocal .withInitial (() -> new byte [4096 ]);
6971 private final ThreadLocal <ByteArrayOutputStream > threadLocalOutStream = ThreadLocal .withInitial (() -> new ByteArrayOutputStream (8256 ));
70- private final Set <WorldChunkKey > pendingChunkLoads = ConcurrentHashMap .newKeySet ();
72+ private final Set <WorldChunkKey > pendingGeneration = ConcurrentHashMap .newKeySet ();
7173
7274 private void logDebug (String message ) {
7375 if (debug ) getLogger ().log (Level .INFO , "[TuffX-Debug] " + message );
@@ -252,6 +254,12 @@ public void run() {
252254 World world = player .getWorld ();
253255
254256 WorldChunkKey key = new WorldChunkKey (world .getName (), vec .getBlockX (), vec .getBlockZ ());
257+
258+ if (pendingGeneration .contains (key )) {
259+ queue .add (vec );
260+ continue ;
261+ }
262+
255263 List <byte []> cachedData = chunkPayloadCache .getIfPresent (key );
256264 if (cachedData != null ) {
257265 sendPayloadsToPlayer (player , cachedData );
@@ -263,7 +271,20 @@ public void run() {
263271 processAndSendChunk (player , world .getChunkAt (vec .getBlockX (), vec .getBlockZ ()));
264272 } else {
265273 world .loadChunk (vec .getBlockX (), vec .getBlockZ (), true );
266- queue .add (vec );
274+ pendingGeneration .add (key );
275+
276+ new BukkitRunnable () {
277+ @ Override
278+ public void run () {
279+ if (player .isOnline () && world .isChunkLoaded (vec .getBlockX (), vec .getBlockZ ())) {
280+ processAndSendChunk (player , world .getChunkAt (vec .getBlockX (), vec .getBlockZ ()));
281+ } else {
282+ logDebug ("Chunk " + key + " was not ready after delay, re-queueing." );
283+ queue .add (vec );
284+ }
285+ pendingGeneration .remove (key );
286+ }
287+ }.runTaskLater (TuffX .this , 5L );
267288 }
268289 }
269290 }
@@ -477,39 +498,85 @@ public void onBlockPlace(BlockPlaceEvent event) {
477498 }
478499
479500 @ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
480- public void onBlockPhysics (BlockPhysicsEvent event ) {
481- Block block = event .getBlock ();
501+ public void onBlockPhysics (BlockPhysicsEvent event ) {
502+ final Block block = event .getBlock ();
503+ if (block .getY () < 0 ) {
504+ final Location loc = block .getLocation ();
505+ final World world = loc .getWorld ();
506+
507+ new BukkitRunnable () {
508+ @ Override
509+ public void run () {
510+ BlockData updatedData = world .getBlockData (loc );
511+ sendSingleBlockUpdate (loc , updatedData );
512+ invalidateChunkCache (world , loc .getBlockX (), loc .getBlockZ ());
513+ }
514+ }.runTask (this );
515+ }
516+ }
517+
518+ @ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
519+ public void onBlockExplode (BlockExplodeEvent event ) {
520+ final Set <WorldChunkKey > affectedChunks = new HashSet <>();
521+ final List <Block > blocksToUpdate = new ArrayList <>(event .blockList ());
522+
523+ new BukkitRunnable () {
524+ @ Override
525+ public void run () {
526+ for (Block block : blocksToUpdate ) {
527+ if (block .getY () < 0 ) {
528+ sendSingleBlockUpdate (block .getLocation (), Material .AIR .createBlockData ());
529+ affectedChunks .add (new WorldChunkKey (block .getWorld ().getName (), block .getX () >> 4 , block .getZ () >> 4 ));
530+ }
531+ }
532+
533+ if (!affectedChunks .isEmpty ()) {
534+ logDebug ("Explosion updated " + affectedChunks .size () + " chunks below y=0." );
535+ affectedChunks .forEach (chunkPayloadCache ::invalidate );
536+ }
537+ }
538+ }.runTask (this );
539+ }
540+
541+ @ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
542+ public void onBlockFromTo (BlockFromToEvent event ) {
543+ final Block block = event .getToBlock ();
544+
482545 if (block .getY () < 0 ) {
483- sendSingleBlockUpdate (block .getLocation (), block .getBlockData ());
484- //sendLightingUpdate(block.getLocation());
485- invalidateChunkCache (block .getWorld (), block .getX (), block .getZ ());
546+ new BukkitRunnable () {
547+ @ Override
548+ public void run () {
549+ sendSingleBlockUpdate (block .getLocation (), block .getBlockData ());
550+ invalidateChunkCache (block .getWorld (), block .getX (), block .getZ ());
551+ }
552+ }.runTask (this );
486553 }
487554 }
488555
489556 private void sendSingleBlockUpdate (Location loc , BlockData data ) {
490557 try (ByteArrayOutputStream bout = new ByteArrayOutputStream (64 );
491558 DataOutputStream out = new DataOutputStream (bout )) {
492-
559+
493560 out .writeUTF ("block_update" );
494561 out .writeInt (loc .getBlockX ());
495562 out .writeInt (loc .getBlockY ());
496563 out .writeInt (loc .getBlockZ ());
497-
564+
498565 int [] legacyData = viablockids .toLegacy (data );
499566 out .writeShort ((short ) ((legacyData [1 ] << 12 ) | (legacyData [0 ] & 0xFFF )));
500-
567+
501568 byte [] payload = bout .toByteArray ();
502-
569+
503570 new BukkitRunnable () {
504571 @ Override
505572 public void run () {
506573 for (Player p : loc .getWorld ().getPlayers ()) {
507- if (p .getLocation ().distanceSquared (loc ) < 4096 ) {
574+ if (p .getLocation ().distanceSquared (loc ) < 4096 ) {
508575 p .sendPluginMessage (TuffX .this , CHANNEL , payload );
509576 }
510577 }
511578 }
512- }.runTaskAsynchronously (this );
579+ }.runTask (this );
513580
514581 } catch (IOException e ) {
515582 getLogger ().severe ("Failed to create single block update payload: " + e .getMessage ());
0 commit comments