@@ -66,11 +66,12 @@ private static boolean hasEnoughDurability(ItemStack tool, int blockCount, Confi
6666
6767 int unbreakingLevel = getUnbreakingLevel (tool );
6868
69+ int damagePerHit = config .isToolDamage () ? config .getToolDamageDecrease () : 1 ;
6970 int estimatedDamage ;
7071 if (config .getRespectUnbreaking () && unbreakingLevel > 0 ) {
71- estimatedDamage = blockCount / (unbreakingLevel + 1 );
72+ estimatedDamage = ( blockCount * damagePerHit ) / (unbreakingLevel + 1 );
7273 } else {
73- estimatedDamage = blockCount * config . getToolDamageDecrease () ;
74+ estimatedDamage = blockCount * damagePerHit ;
7475 }
7576
7677 return remainingDurability > estimatedDamage ;
@@ -98,7 +99,7 @@ private static void applyToolDamage(ItemStack tool, Player player, int blocksBro
9899 int newDamage = currentDamage + damageToApply ;
99100
100101 if (newDamage >= tool .getType ().getMaxDurability ()) {
101- player .getInventory ().removeItem ( tool );
102+ player .getInventory ().setItemInMainHand ( null );
102103 } else {
103104 damageableMeta .setDamage (newDamage );
104105 tool .setItemMeta (damageableMeta );
@@ -116,7 +117,7 @@ private static boolean shouldApplyDurabilityLoss(int unbreakingLevel, Config con
116117 if (unbreakingLevel <= 0 || !config .getRespectUnbreaking ()) {
117118 return true ;
118119 }
119- return random .nextInt (100 ) < ( 100.0 / ( unbreakingLevel + 1 )) ;
120+ return random .nextInt (unbreakingLevel + 1 ) == 0 ;
120121 }
121122
122123 public static boolean isTool (Player player ) {
@@ -140,10 +141,6 @@ public static boolean isTool(Player player) {
140141 return xMat == XMaterial .SHEARS || xMat == XMaterial .FISHING_ROD || xMat == XMaterial .FLINT_AND_STEEL ;
141142 }
142143
143- /**
144- * Main entry point for tree chopping
145- * PHASE 1: Synchronous snapshot creation
146- */
147144 public void chopTree (
148145 Block block ,
149146 Player player ,
@@ -180,14 +177,12 @@ public void chopTree(
180177 // Mark location as processing
181178 sessionManager .addTreeChopLocations (playerUUID , Collections .singleton (block .getLocation ()));
182179
183- // PHASE 1: Synchronous - Capture block snapshot
184180 try {
185181 BlockSnapshot treeSnapshot = BlockSnapshotCreator .captureTreeRegion (
186182 block , config , connectedBlocks , config .getMaxDiscoveryBlocks ());
187183
188184 Location startLocation = block .getLocation ().clone ();
189185
190- // PHASE 2: Asynchronous - Calculate tree structure
191186 Runnable asyncDiscovery = () -> {
192187 try {
193188 Set <Location > treeBlocks = BlockDiscoveryUtils .discoverTreeBFS (
@@ -215,10 +210,6 @@ public void chopTree(
215210 }
216211 }
217212
218- /**
219- * Validate tree and execute chopping
220- * This runs synchronously on the region thread
221- */
222213 private void validateAndExecuteChop (
223214 Set <Location > treeBlocks ,
224215 Block originalBlock ,
@@ -235,7 +226,6 @@ private void validateAndExecuteChop(
235226 return ;
236227 }
237228
238- // Validation checks
239229 if (treeBlocks .isEmpty ()) {
240230 sessionManager .clearTreeChopSession (playerUUID );
241231 return ;
@@ -267,10 +257,6 @@ private void validateAndExecuteChop(
267257 executeTreeChop (treeBlocks , player , tool , config , playerConfig , hooks , originalBlock );
268258 }
269259
270- /**
271- * Execute tree chopping in batches
272- * This runs synchronously with batch processing
273- */
274260 private void executeTreeChop (
275261 Set <Location > treeBlocks ,
276262 Player player ,
@@ -285,13 +271,10 @@ private void executeTreeChop(
285271 int totalBlocks = blockList .size ();
286272 UUID playerUUID = player .getUniqueId ();
287273
288- // Track the LOWEST log of each type for replanting (Y coordinate)
274+ Location centerLocation = originalBlock . getLocation (). clone ();
289275 Map <Material , Location > logTypesForReplant = new HashMap <>();
290- // Use thread-safe set for actuallyRemovedLogs since it's accessed across batches
291276 Set <Location > actuallyRemovedLogs = ConcurrentHashMap .newKeySet ();
292277
293- // CRITICAL: Capture leaf snapshot BEFORE removing logs
294- // This ensures we can see which logs exist for proper leaf orphan detection
295278 BlockSnapshot leafSnapshot = null ;
296279 if (config .isLeafRemovalEnabled ()) {
297280 try {
@@ -311,7 +294,7 @@ private void executeTreeChop(
311294 (location , index ) -> {
312295 Block block = location .getBlock ();
313296
314- // Re-check block type (may have changed)
297+ // Re-check block type (may have changed between phases )
315298 if (!BlockDiscoveryUtils .isLog (block .getType (), config )) {
316299 return ;
317300 }
@@ -323,7 +306,7 @@ private void executeTreeChop(
323306
324307 Material originalLogType = block .getType ();
325308
326- // Track the lowest Y coordinate log for each type ( for proper replanting)
309+ // Track the lowest-Y log of each type for replanting
327310 Location existingLoc = logTypesForReplant .get (originalLogType );
328311 if (existingLoc == null || location .getBlockY () < existingLoc .getBlockY ()) {
329312 logTypesForReplant .put (originalLogType , location .clone ());
@@ -355,20 +338,17 @@ private void executeTreeChop(
355338 // Handle leaf removal
356339 if (config .isLeafRemovalEnabled () && finalLeafSnapshot != null ) {
357340 long delay = config .getLeafRemovalDelayTicks ();
358- Location leafProcessLocation = originalBlock .getLocation ();
359341
360- Runnable leafTask = () -> {
361- processLeafRemovalWithPreCapturedSnapshot (
362- finalLeafSnapshot ,
363- originalBlock .getLocation (),
364- player ,
365- config ,
366- playerConfig ,
367- hooks ,
368- actuallyRemovedLogs );
369- };
342+ Runnable leafTask = () -> processLeafRemovalWithPreCapturedSnapshot (
343+ finalLeafSnapshot ,
344+ centerLocation ,
345+ player ,
346+ config ,
347+ playerConfig ,
348+ hooks ,
349+ actuallyRemovedLogs );
370350
371- scheduler .scheduleDelayed (leafProcessLocation , leafTask , delay );
351+ scheduler .scheduleDelayed (centerLocation , leafTask , delay );
372352 }
373353
374354 // Handle replanting
@@ -399,13 +379,6 @@ private void executeTreeChop(
399379 });
400380 }
401381
402- /**
403- * Process leaf removal with PRE-CAPTURED snapshot
404- * The snapshot was taken BEFORE logs were removed
405- * PHASE 1: Already done (snapshot captured before log removal)
406- * PHASE 2: Async - Calculate leaves to remove
407- * PHASE 3: Sync - Remove leaves in batches
408- */
409382 private void processLeafRemovalWithPreCapturedSnapshot (
410383 BlockSnapshot leafSnapshot ,
411384 Location centerLocation ,
@@ -425,36 +398,28 @@ private void processLeafRemovalWithPreCapturedSnapshot(
425398
426399 String playerKey = player .getUniqueId ().toString ();
427400
428- // Check if player already has an active leaf removal session
429401 if (sessionManager .hasActiveLeafRemovalSession (playerKey )) {
430402 return ;
431403 }
432404
433- // Start a new session
434405 String sessionId = sessionManager .startLeafRemovalSession (playerKey );
435406 if (sessionId == null ) {
436407 return ;
437408 }
438409
439- // PHASE 2: Asynchronous - Calculate leaves to remove
440410 Runnable asyncLeafCalculation = () -> {
441411 try {
442- // Use the provided removedLogs directly
443- // (already contains all actually removed logs from executeTreeChop)
444412 Set <Location > leavesToRemove ;
445413 int radius = config .getLeafRemovalRadius ();
446414
447- // Choose discovery method based on radius and mode
448415 if ("smart" .equalsIgnoreCase (config .getLeafRemovalMode ())) {
449416 leavesToRemove = BlockDiscoveryUtils .discoverLeavesBFS (
450417 leafSnapshot , centerLocation , radius , config , removedLogs );
451418 } else {
452- // For "aggressive" or "radius" mode, radial is faster
453419 leavesToRemove = BlockDiscoveryUtils .discoverLeavesRadial (
454420 leafSnapshot , centerLocation , radius , config , removedLogs );
455421 }
456422
457- // PHASE 3: Back to sync for removal
458423 Runnable removalTask = () ->
459424 executeLeafRemoval (leavesToRemove , player , config , playerConfig , hooks , sessionId , playerKey );
460425
@@ -470,15 +435,10 @@ private void processLeafRemovalWithPreCapturedSnapshot(
470435 if (config .isLeafRemovalAsync ()) {
471436 scheduler .runTaskAsync (asyncLeafCalculation );
472437 } else {
473- // Run synchronously if async is disabled
474438 asyncLeafCalculation .run ();
475439 }
476440 }
477441
478- /**
479- * Execute leaf removal in batches
480- * This runs synchronously on the region thread
481- */
482442 private void executeLeafRemoval (
483443 Set <Location > leavesToRemove ,
484444 Player player ,
@@ -501,31 +461,20 @@ private void executeLeafRemoval(
501461 0 ,
502462 batchSize ,
503463 (location , index ) -> {
504- // Check daily limit if counting towards limit
505464 if (config .getLeafRemovalCountsTowardsLimit ()) {
506465 if (!PermissionUtils .hasVipBlock (player , playerConfig , config )
507466 && playerConfig .getDailyBlocksBroken () >= config .getMaxBlocksPerDay ()) {
508- return false ; // Stop processing - limit reached
467+ return false ;
509468 }
510469 }
511470
512471 Block leafBlock = location .getBlock ();
513-
514- // Remove the leaf block with all checks
515472 removeLeafBlock (leafBlock , player , config , playerConfig , hooks );
516-
517- return true ; // Continue processing
473+ return true ;
518474 },
519- () -> {
520- // Leaf removal complete - end session
521- sessionManager .endLeafRemovalSession (sessionId , playerKey );
522- });
475+ () -> sessionManager .endLeafRemovalSession (sessionId , playerKey ));
523476 }
524477
525- /**
526- * Remove a single leaf block with all necessary checks
527- * This runs synchronously on the region thread
528- */
529478 private boolean removeLeafBlock (
530479 Block leafBlock ,
531480 Player player ,
@@ -535,26 +484,19 @@ private boolean removeLeafBlock(
535484
536485 Location leafLocation = leafBlock .getLocation ();
537486
538- // Check if already processing this location
539- if (processingLeafLocations .contains (leafLocation )) {
540- return false ;
541- }
542-
543- // Re-check if it's still a leaf
544- if (!BlockDiscoveryUtils .isLeafBlock (leafBlock .getType (), config )) {
487+ if (!processingLeafLocations .add (leafLocation )) {
545488 return false ;
546489 }
547490
548- // Re-check protection at execution time
549- if (!ProtectionCheckUtils . canModifyBlock ( player , leafLocation , hooks )) {
550- return false ;
551- }
491+ try {
492+ if (!BlockDiscoveryUtils . isLeafBlock ( leafBlock . getType (), config )) {
493+ return false ;
494+ }
552495
553- // Mark as processing
554- processingLeafLocations .add (leafLocation );
496+ if (!ProtectionCheckUtils .canModifyBlock (player , leafLocation , hooks )) {
497+ return false ;
498+ }
555499
556- try {
557- // Call BlockBreakEvent if enabled
558500 if (config .isCallBlockBreakEvent ()) {
559501 BlockBreakEvent breakEvent = new BlockBreakEvent (leafBlock , player );
560502 plugin .getServer ().getPluginManager ().callEvent (breakEvent );
@@ -573,15 +515,13 @@ private boolean removeLeafBlock(
573515 leafBlock .setType (XMaterial .AIR .get (), false );
574516 }
575517
576- // Update daily blocks count if needed
577518 if (config .getLeafRemovalCountsTowardsLimit ()) {
578519 playerConfig .incrementDailyBlocksBroken ();
579520 }
580521
581522 return true ;
582523
583524 } finally {
584- // Always remove from processing set
585525 processingLeafLocations .remove (leafLocation );
586526 }
587527 }
0 commit comments