From dd5cd3df4a396ec24e950e8f48ba762a0fc2ad29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zhenyuan=E2=9C=A8?= Date: Sat, 4 Apr 2026 16:16:52 +0800 Subject: [PATCH 01/13] feat: Added increment-block-statistics --- src/main/java/org/milkteamc/autotreechop/Config.java | 6 ++++++ .../org/milkteamc/autotreechop/utils/TreeChopUtils.java | 9 +++++++++ src/main/resources/config.yml | 3 +++ 3 files changed, 18 insertions(+) diff --git a/src/main/java/org/milkteamc/autotreechop/Config.java b/src/main/java/org/milkteamc/autotreechop/Config.java index d39dfe5..bd1e0f3 100644 --- a/src/main/java/org/milkteamc/autotreechop/Config.java +++ b/src/main/java/org/milkteamc/autotreechop/Config.java @@ -105,6 +105,7 @@ public class Config { private int maxTreeSize; private int maxDiscoveryBlocks; private boolean callBlockBreakEvent; + private boolean incrementBlockStatistics; public Config(AutoTreeChop plugin) { this.plugin = plugin; @@ -228,6 +229,7 @@ private void loadValues() { maxTreeSize = config.getInt("max-tree-size", 500); maxDiscoveryBlocks = config.getInt("max-discovery-blocks", 1000); callBlockBreakEvent = config.getBoolean("call-block-break-event", true); + incrementBlockStatistics = config.getBoolean("increment-block-statistics", false); autoReplantEnabled = config.getBoolean("enable-auto-replant", true); replantDelayTicks = config.getLong("replant-delay-ticks", 15L); @@ -546,6 +548,10 @@ public boolean isCallBlockBreakEvent() { return callBlockBreakEvent; } + public boolean isIncrementBlockStatistics() { + return incrementBlockStatistics; + } + public int getIdleTimeoutSeconds() { return idleTimeoutSeconds; } diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 880b618..50f3c7e 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -341,6 +341,10 @@ private void executeTreeChop( } block.breakNaturally(); + if (config.isIncrementBlockStatistics()) { + player.incrementStatistic(org.bukkit.Statistic.MINE_BLOCK, originalLogType); + } + actuallyRemovedLogs.add(location); sessionManager.trackRemovedLogForPlayer(playerUUID.toString(), location); playerConfig.incrementDailyBlocksBroken(); @@ -566,6 +570,7 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } + Material leafMaterial = leafBlock.getType(); if (config.getLeafRemovalDropItems()) { leafBlock.breakNaturally(); } else { @@ -577,6 +582,10 @@ private boolean removeLeafBlock( } } + if (config.isIncrementBlockStatistics()) { + player.incrementStatistic(org.bukkit.Statistic.MINE_BLOCK, leafMaterial); + } + // Update daily blocks count if needed if (config.getLeafRemovalCountsTowardsLimit()) { playerConfig.incrementDailyBlocksBroken(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ed7344e..1c1217f 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -83,6 +83,9 @@ max-discovery-blocks: 1000 # Call BlockBreakEvent for each block # true = Better plugin compatibility call-block-break-event: true +# Increment player's Minecraft block-break statistic (Statistic.MINE_BLOCK) for every block broken by ATC, +# including all chain-chopped logs and leaves (if leaf removal is enabled). +increment-block-statistics: false # Protection plugins setting # If you are using Residence, you can set which Flag players have access to AutoTreeChop in residence. From f169ca658a243ff07235ea473be54ab157341f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zhenyuan=E2=9C=A8?= Date: Sat, 4 Apr 2026 17:06:36 +0800 Subject: [PATCH 02/13] perf: Batch incrementStatistic calls by material type --- .../autotreechop/utils/TreeChopUtils.java | 26 +++++++++++++++---- src/main/resources/config.yml | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 50f3c7e..fc7e1b9 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -33,6 +33,8 @@ import org.milkteamc.autotreechop.Config; import org.milkteamc.autotreechop.PlayerConfig; +import static org.bukkit.Statistic.MINE_BLOCK; + public class TreeChopUtils { private static final Random random = new Random(); @@ -303,6 +305,7 @@ private void executeTreeChop( BlockSnapshot finalLeafSnapshot = leafSnapshot; + Map logStatCounts = new HashMap<>(); batchProcessor.processBatch( blockList, 0, @@ -342,7 +345,7 @@ private void executeTreeChop( block.breakNaturally(); if (config.isIncrementBlockStatistics()) { - player.incrementStatistic(org.bukkit.Statistic.MINE_BLOCK, originalLogType); + logStatCounts.merge(originalLogType, 1, Integer::sum); } actuallyRemovedLogs.add(location); @@ -351,6 +354,11 @@ private void executeTreeChop( }, () -> { // After all logs are removed + if (config.isIncrementBlockStatistics()) { + logStatCounts.forEach((mat, count) -> + player.incrementStatistic(MINE_BLOCK, mat, count)); + } + if (config.isToolDamage()) { applyToolDamage(tool, player, totalBlocks, config); } @@ -498,6 +506,7 @@ private void executeLeafRemoval( List leafList = new ArrayList<>(leavesToRemove); int batchSize = config.getLeafRemovalBatchSize(); + Map leafStatCounts = new HashMap<>(); batchProcessor.processBatchWithTermination( leafList, @@ -515,11 +524,17 @@ private void executeLeafRemoval( Block leafBlock = location.getBlock(); // Remove the leaf block with all checks - removeLeafBlock(leafBlock, player, config, playerConfig, hooks); + removeLeafBlock(leafBlock, player, config, playerConfig, hooks, leafStatCounts); return true; // Continue processing }, () -> { + // Flush accumulated leaf statistics + if (config.isIncrementBlockStatistics()) { + leafStatCounts.forEach((mat, count) -> + player.incrementStatistic(MINE_BLOCK, mat, count)); + } + // Leaf removal complete - end session sessionManager.endLeafRemovalSession(sessionId, playerKey); }); @@ -534,7 +549,8 @@ private boolean removeLeafBlock( Player player, Config config, PlayerConfig playerConfig, - ProtectionCheckUtils.ProtectionHooks hooks) { + ProtectionCheckUtils.ProtectionHooks hooks, + Map leafStatCounts) { Location leafLocation = leafBlock.getLocation(); @@ -570,7 +586,6 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } - Material leafMaterial = leafBlock.getType(); if (config.getLeafRemovalDropItems()) { leafBlock.breakNaturally(); } else { @@ -583,7 +598,8 @@ private boolean removeLeafBlock( } if (config.isIncrementBlockStatistics()) { - player.incrementStatistic(org.bukkit.Statistic.MINE_BLOCK, leafMaterial); + Material leafMaterial = leafBlock.getType(); + leafStatCounts.merge(leafMaterial, 1, Integer::sum); } // Update daily blocks count if needed diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1c1217f..8f70223 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -83,7 +83,7 @@ max-discovery-blocks: 1000 # Call BlockBreakEvent for each block # true = Better plugin compatibility call-block-break-event: true -# Increment player's Minecraft block-break statistic (Statistic.MINE_BLOCK) for every block broken by ATC, +# Increment player's Minecraft block-break statistic (Statistic.MINE_BLOCK) for every block broken by AutoTreeChop, # including all chain-chopped logs and leaves (if leaf removal is enabled). increment-block-statistics: false From 6ebc68c58a6d5105277271d490998abdab80512d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 07:16:14 +0000 Subject: [PATCH 03/13] [feat] auto pickup chopped drops into the player inventory Chopped blocks were always dropped on the ground with breakNaturally(), which leaves logs scattered around the top of a large tree where the player cannot reach them. Adds an opt-in auto pickup path: drops are read with block.getDrops(tool, player) before the block is cleared, so Fortune, Silk Touch and the tool type are respected exactly like a vanilla break. Both the log phase and the leaf phase (when leaf-removal-drop-items is on) are covered; the log the player originally broke is included since it is part of the BFS result and goes through the same path. Drops are accumulated during the batches and handed over once in the batch completion callback, next to the existing statistic flush and tool damage. That keeps inventory mutation out of the per-block loop and means at most one "inventory full" message per chop. Whatever does not fit is dropped at the player's feet. Gated behind enable-auto-pickup (default false, so existing servers are unaffected) and the new autotreechop.autopickup permission (default true), following the existing config-flag-and-permission pattern. Since setType(AIR) shows no break particles where breakNaturally() did, EffectUtils.showBlockBreakEffect() restores them under the existing visual-effect option. It emits particles only, so it does not double up on the playBreakSound option. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016MjYARRStYz4VsBfGJuAHi --- README.md | 2 + .../org/milkteamc/autotreechop/Config.java | 6 + .../milkteamc/autotreechop/MessageKeys.java | 1 + .../events/PlayerQuitListener.java | 3 + .../utils/DropCollectionUtils.java | 171 ++++++++++++++++++ .../autotreechop/utils/EffectUtils.java | 32 ++++ .../autotreechop/utils/TreeChopUtils.java | 69 +++++-- src/main/resources/config.yml | 5 + src/main/resources/lang/en.properties | 2 + src/main/resources/lang/zh.properties | 2 + src/main/resources/plugin.yml | 2 + 11 files changed, 281 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java diff --git a/README.md b/README.md index 64a0763..4f9dfd8 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo - Toggle on/off with `/atc` command or by sneaking (pressing SHIFT) - Async support for smooth performance on Modern servers - Customizable leaves cleaner +- Optional auto pickup: send drops straight to the player's inventory, respecting Fortune and Silk Touch ### ⚡ Lightweight & Easy to Configure @@ -92,6 +93,7 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo | `autotreechop.updatechecker` | Receive update notifications | OP | | `autotreechop.replant` | Enable auto replanting | Everyone | | `autotreechop.leaves` | Enable leaves removal | Everyone | +| `autotreechop.autopickup` | Collect chopped drops straight into the inventory | Everyone | --- diff --git a/src/main/java/org/milkteamc/autotreechop/Config.java b/src/main/java/org/milkteamc/autotreechop/Config.java index d3834e1..5604539 100644 --- a/src/main/java/org/milkteamc/autotreechop/Config.java +++ b/src/main/java/org/milkteamc/autotreechop/Config.java @@ -105,6 +105,7 @@ public class Config { private int maxDiscoveryBlocks; private boolean callBlockBreakEvent; private boolean incrementBlockStatistics; + private boolean autoPickupEnabled; public Config(AutoTreeChop plugin) { this.plugin = plugin; @@ -229,6 +230,7 @@ private void loadValues() { maxDiscoveryBlocks = config.getInt("max-discovery-blocks", 1000); callBlockBreakEvent = config.getBoolean("call-block-break-event", true); incrementBlockStatistics = config.getBoolean("increment-block-statistics", false); + autoPickupEnabled = config.getBoolean("enable-auto-pickup", false); autoReplantEnabled = config.getBoolean("enable-auto-replant", true); replantDelayTicks = config.getLong("replant-delay-ticks", 15L); @@ -543,6 +545,10 @@ public boolean isIncrementBlockStatistics() { return incrementBlockStatistics; } + public boolean isAutoPickupEnabled() { + return autoPickupEnabled; + } + public int getIdleTimeoutSeconds() { return idleTimeoutSeconds; } diff --git a/src/main/java/org/milkteamc/autotreechop/MessageKeys.java b/src/main/java/org/milkteamc/autotreechop/MessageKeys.java index 3eb53e7..44ad32c 100644 --- a/src/main/java/org/milkteamc/autotreechop/MessageKeys.java +++ b/src/main/java/org/milkteamc/autotreechop/MessageKeys.java @@ -45,6 +45,7 @@ private MessageKeys() {} public static final String NO_PENDING_CONFIRMATION = "noPendingConfirmation"; public static final String ALREADY_ENABLED = "alreadyEnabled"; public static final String ALREADY_DISABLED = "alreadyDisabled"; + public static final String INVENTORY_FULL = "inventoryFull"; public static final String ABOUT_HEADER = "aboutHeader"; public static final String ABOUT_LICENSE = "aboutLicense"; public static final String ABOUT_GITHUB = "aboutGithub"; diff --git a/src/main/java/org/milkteamc/autotreechop/events/PlayerQuitListener.java b/src/main/java/org/milkteamc/autotreechop/events/PlayerQuitListener.java index 4fdcfe1..97eb600 100644 --- a/src/main/java/org/milkteamc/autotreechop/events/PlayerQuitListener.java +++ b/src/main/java/org/milkteamc/autotreechop/events/PlayerQuitListener.java @@ -24,6 +24,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.milkteamc.autotreechop.AutoTreeChop; import org.milkteamc.autotreechop.PlayerConfig; +import org.milkteamc.autotreechop.utils.DropCollectionUtils; import org.milkteamc.autotreechop.utils.SessionManager; public class PlayerQuitListener implements Listener { @@ -49,5 +50,7 @@ public void onPlayerQuit(PlayerQuitEvent event) { // Clear all confirmation state so memory doesn't leak between sessions. plugin.getConfirmationManager().clearPlayer(playerUUID); + + DropCollectionUtils.clearPlayerData(playerUUID); } } diff --git a/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java new file mode 100644 index 0000000..5204935 --- /dev/null +++ b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2026 MilkTeaMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.milkteamc.autotreechop.utils; + +import com.cryptomorin.xseries.XMaterial; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.milkteamc.autotreechop.AutoTreeChop; +import org.milkteamc.autotreechop.Config; +import org.milkteamc.autotreechop.MessageKeys; + +/** + * Collects the drops of blocks broken by AutoTreeChop and hands them straight to the player + * instead of letting them fall on the ground. + * + *

Drops are gathered with {@link Block#getDrops(ItemStack, org.bukkit.entity.Entity)} so + * enchantments (Fortune, Silk Touch) and the tool type are respected exactly like a vanilla break. + * + *

Collection and delivery are deliberately split: {@code collectDrops} is called per block while + * a batch is running, {@code deliverDrops} once when the batch completes. That keeps inventory + * mutation out of the per-block loop and means a player sees at most one "inventory full" message + * per chop instead of one per log. + */ +public final class DropCollectionUtils { + + private static final Map lastInventoryFullMessage = new ConcurrentHashMap<>(); + private static final long INVENTORY_FULL_MESSAGE_COOLDOWN_MS = 5000L; + + private DropCollectionUtils() {} + + /** + * Auto pickup requires both the config flag and the permission node, following the same + * pattern as {@link TreeReplantUtils#isReplantEnabledForPlayer(Player, Config)}. + */ + public static boolean isAutoPickupEnabledForPlayer(Player player, Config config) { + return config.isAutoPickupEnabled() && player.hasPermission("autotreechop.autopickup"); + } + + /** + * Gathers the drops of a block into {@code accumulator}. + * + *

MUST be called BEFORE the block is removed, otherwise the block is already air and + * yields nothing. Stacks are merged in place so a 500 log tree produces a handful of full + * stacks rather than 500 single item stacks. + */ + public static void collectDrops(Block block, ItemStack tool, Player player, List accumulator) { + Collection drops; + + if (tool == null || XMaterial.matchXMaterial(tool) == XMaterial.AIR) { + drops = block.getDrops(); + } else { + drops = block.getDrops(tool, player); + } + + for (ItemStack drop : drops) { + if (drop == null || drop.getAmount() <= 0) { + continue; + } + merge(accumulator, drop.clone()); + } + } + + /** + * Puts everything collected into the player's inventory. Whatever does not fit is dropped at + * the player's feet and the player is told once (rate limited, because logs and leaves are + * delivered in two separate phases). + * + *

The accumulator is cleared so the same list can be reused across phases. + */ + public static void deliverDrops(Player player, List accumulator) { + if (accumulator.isEmpty()) { + return; + } + + World world = player.getWorld(); + Location dropLocation = player.getLocation(); + + if (!player.isOnline()) { + // Player left mid-chop: nothing to add to, so put it all on the ground. + for (ItemStack stack : accumulator) { + world.dropItemNaturally(dropLocation, stack); + } + accumulator.clear(); + return; + } + + Map leftovers = player.getInventory().addItem(accumulator.toArray(new ItemStack[0])); + accumulator.clear(); + + if (leftovers.isEmpty()) { + return; + } + + for (ItemStack leftover : leftovers.values()) { + world.dropItemNaturally(dropLocation, leftover); + } + + notifyInventoryFull(player); + } + + private static void merge(List accumulator, ItemStack drop) { + int maxStackSize = drop.getMaxStackSize(); + + if (maxStackSize <= 0) { + // Bukkit returns -1 when it cannot determine a stack size; keep the drop as-is. + accumulator.add(drop); + return; + } + + for (ItemStack existing : accumulator) { + if (existing.getAmount() >= maxStackSize || !existing.isSimilar(drop)) { + continue; + } + + int space = maxStackSize - existing.getAmount(); + int moved = Math.min(space, drop.getAmount()); + existing.setAmount(existing.getAmount() + moved); + drop.setAmount(drop.getAmount() - moved); + + if (drop.getAmount() <= 0) { + return; + } + } + + accumulator.add(drop); + } + + private static void notifyInventoryFull(Player player) { + UUID playerUUID = player.getUniqueId(); + long now = System.currentTimeMillis(); + Long last = lastInventoryFullMessage.get(playerUUID); + + if (last != null && now - last < INVENTORY_FULL_MESSAGE_COOLDOWN_MS) { + return; + } + + lastInventoryFullMessage.put(playerUUID, now); + AutoTreeChop.sendMessage(player, MessageKeys.INVENTORY_FULL); + } + + /** + * Drops the cached "inventory full" timestamp for a player. Called on quit so the map does not + * grow without bound. + */ + public static void clearPlayerData(UUID playerUUID) { + lastInventoryFullMessage.remove(playerUUID); + } +} diff --git a/src/main/java/org/milkteamc/autotreechop/utils/EffectUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/EffectUtils.java index b744bbb..263b01e 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/EffectUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/EffectUtils.java @@ -41,6 +41,38 @@ public static void sendMaxBlockLimitReachedMessage(Player player, Block block) { .spawn(); } + /** + * Block break particles for a block that AutoTreeChop removes with {@code setType(AIR)} rather + * than {@code breakNaturally()} — used by auto pickup, which reads the drops itself and would + * otherwise clear the block with no visual feedback at all. + * + *

Particles only: the break sound stays under the {@code playBreakSound} option so this does + * not double up on it. + */ + public static void showBlockBreakEffect(Block block) { + if (!XMaterial.supports(13)) { + return; + } + + try { + XMaterial blockMaterial = XMaterial.matchXMaterial(block.getType()); + if (blockMaterial == null || blockMaterial.get() == null) { + return; + } + + ParticleDisplay.of(XParticle.BLOCK) + .withLocation(block.getLocation().add(0.5, 0.5, 0.5)) + .withBlock(blockMaterial.get().createBlockData()) + .withCount(15) + .offset(0.3, 0.3, 0.3) + .spawn(); + } catch (NoSuchMethodError | UnsupportedOperationException e) { + // The BLOCK particle API changed between MC versions; XSeries could not provide a + // compatible implementation on this server. Purely cosmetic, so degrade gracefully. + LOGGER.fine("BLOCK particle unavailable for block break effect on this server version: " + e.getMessage()); + } + } + public static void showChopEffect(Player player, Block block) { ParticleDisplay.of(XParticle.HAPPY_VILLAGER) .withLocation(block.getLocation().add(0.5, 0.5, 0.5)) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index f4daaf1..1cd6b2a 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -17,6 +17,8 @@ package org.milkteamc.autotreechop.utils; +import static org.bukkit.Statistic.MINE_BLOCK; + import com.cryptomorin.xseries.XEnchantment; import com.cryptomorin.xseries.XMaterial; import com.cryptomorin.xseries.XSound; @@ -34,8 +36,6 @@ import org.milkteamc.autotreechop.MessageKeys; import org.milkteamc.autotreechop.PlayerConfig; -import static org.bukkit.Statistic.MINE_BLOCK; - public class TreeChopUtils { private static final Random random = new Random(); @@ -307,6 +307,9 @@ private void executeTreeChop( BlockSnapshot finalLeafSnapshot = leafSnapshot; Map logStatCounts = new HashMap<>(); + boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, config); + List collectedDrops = new ArrayList<>(); + batchProcessor.processBatch( blockList, 0, @@ -343,7 +346,18 @@ private void executeTreeChop( if (config.getPlayBreakSound()) { XSound.BLOCK_WOOD_BREAK.play(location, 1.0f, 1.0f); } - block.breakNaturally(); + + if (autoPickup) { + // Drops must be read before the block is cleared, otherwise it is already air. + DropCollectionUtils.collectDrops(block, tool, player, collectedDrops); + if (config.isVisualEffect()) { + // breakNaturally() shows break particles for free; setType() does not. + EffectUtils.showBlockBreakEffect(block); + } + block.setType(XMaterial.AIR.get(), false); + } else { + block.breakNaturally(); + } if (config.isIncrementBlockStatistics()) { logStatCounts.merge(originalLogType, 1, Integer::sum); @@ -356,14 +370,17 @@ private void executeTreeChop( () -> { // After all logs are removed if (config.isIncrementBlockStatistics()) { - logStatCounts.forEach((mat, count) -> - player.incrementStatistic(MINE_BLOCK, mat, count)); + logStatCounts.forEach((mat, count) -> player.incrementStatistic(MINE_BLOCK, mat, count)); } if (config.isToolDamage()) { applyToolDamage(tool, player, totalBlocks, config); } + if (autoPickup) { + DropCollectionUtils.deliverDrops(player, collectedDrops); + } + // Handle leaf removal if (config.isLeafRemovalEnabled() && finalLeafSnapshot != null) { long delay = config.getLeafRemovalDelayTicks(); @@ -374,6 +391,7 @@ private void executeTreeChop( finalLeafSnapshot, originalBlock.getLocation(), player, + tool, config, playerConfig, hooks, @@ -422,6 +440,7 @@ private void processLeafRemovalWithPreCapturedSnapshot( BlockSnapshot leafSnapshot, Location centerLocation, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, ProtectionCheckUtils.ProtectionHooks hooks, @@ -467,8 +486,8 @@ private void processLeafRemovalWithPreCapturedSnapshot( } // PHASE 3: Back to sync for removal - Runnable removalTask = () -> - executeLeafRemoval(leavesToRemove, player, config, playerConfig, hooks, sessionId, playerKey); + Runnable removalTask = () -> executeLeafRemoval( + leavesToRemove, player, tool, config, playerConfig, hooks, sessionId, playerKey); scheduler.runTaskAtLocation(centerLocation, removalTask); @@ -494,6 +513,7 @@ private void processLeafRemovalWithPreCapturedSnapshot( private void executeLeafRemoval( Set leavesToRemove, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, ProtectionCheckUtils.ProtectionHooks hooks, @@ -508,6 +528,8 @@ private void executeLeafRemoval( List leafList = new ArrayList<>(leavesToRemove); int batchSize = config.getLeafRemovalBatchSize(); Map leafStatCounts = new HashMap<>(); + boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, config); + List collectedDrops = new ArrayList<>(); batchProcessor.processBatchWithTermination( leafList, @@ -525,15 +547,27 @@ private void executeLeafRemoval( Block leafBlock = location.getBlock(); // Remove the leaf block with all checks - removeLeafBlock(leafBlock, player, config, playerConfig, hooks, leafStatCounts); + removeLeafBlock( + leafBlock, + player, + tool, + config, + playerConfig, + hooks, + leafStatCounts, + autoPickup, + collectedDrops); return true; // Continue processing }, () -> { // Flush accumulated leaf statistics if (config.isIncrementBlockStatistics()) { - leafStatCounts.forEach((mat, count) -> - player.incrementStatistic(MINE_BLOCK, mat, count)); + leafStatCounts.forEach((mat, count) -> player.incrementStatistic(MINE_BLOCK, mat, count)); + } + + if (autoPickup) { + DropCollectionUtils.deliverDrops(player, collectedDrops); } // Leaf removal complete - end session @@ -548,10 +582,13 @@ private void executeLeafRemoval( private boolean removeLeafBlock( Block leafBlock, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, ProtectionCheckUtils.ProtectionHooks hooks, - Map leafStatCounts) { + Map leafStatCounts, + boolean autoPickup, + List collectedDrops) { Location leafLocation = leafBlock.getLocation(); @@ -587,10 +624,14 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } - if (config.getLeafRemovalDropItems()) { - leafBlock.breakNaturally(); - } else { + if (!config.getLeafRemovalDropItems()) { + leafBlock.setType(XMaterial.AIR.get(), false); + } else if (autoPickup) { + // Drops must be read before the block is cleared, otherwise it is already air. + DropCollectionUtils.collectDrops(leafBlock, tool, player, collectedDrops); leafBlock.setType(XMaterial.AIR.get(), false); + } else { + leafBlock.breakNaturally(); } if (config.isIncrementBlockStatistics()) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 8f70223..d7d11b8 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -86,6 +86,11 @@ call-block-break-event: true # Increment player's Minecraft block-break statistic (Statistic.MINE_BLOCK) for every block broken by AutoTreeChop, # including all chain-chopped logs and leaves (if leaf removal is enabled). increment-block-statistics: false +# Put chopped blocks straight into the player's inventory instead of dropping them on the ground. +# Respects the tool used and its enchantments (Fortune, Silk Touch), just like a normal block break. +# Items that do not fit are dropped at the player's feet. +# Also requires the "autotreechop.autopickup" permission. +enable-auto-pickup: false # Protection plugins setting # If you are using Residence, you can set which Flag players have access to AutoTreeChop in residence. diff --git a/src/main/resources/lang/en.properties b/src/main/resources/lang/en.properties index 3c1f47c..3c01bef 100644 --- a/src/main/resources/lang/en.properties +++ b/src/main/resources/lang/en.properties @@ -33,6 +33,8 @@ sneakDisabled=AutoTreeChop disabled after stopping sneak. alreadyEnabled=AutoTreeChop is already enabled. alreadyDisabled=AutoTreeChop is already disabled. +inventoryFull=Your inventory is full. The remaining items were dropped at your feet. + consoleName=console aboutHeader=AutoTreeChop - v{version} by the MilkTeaMC team and contributors diff --git a/src/main/resources/lang/zh.properties b/src/main/resources/lang/zh.properties index ba40d33..f846214 100644 --- a/src/main/resources/lang/zh.properties +++ b/src/main/resources/lang/zh.properties @@ -33,6 +33,8 @@ sneakDisabled=已離開潛行狀態,自動砍樹已停用。 alreadyEnabled=自動砍樹已經是啟用狀態。 alreadyDisabled=自動砍樹已經是停用狀態。 +inventoryFull=你的背包已滿,剩餘的物品掉落在你腳邊。 + consoleName=控制台 aboutHeader=AutoTreeChop - v{version} 由 MilkTeaMC 團隊與貢獻者開發 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 23547d7..1524479 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,6 +14,8 @@ permissions: default: true autotreechop.leaves: default: true + autotreechop.autopickup: + default: true autotreechop.vip: default: op autotreechop.updatechecker: From 41bb5764f10b6ab8aee70149e1419ed6ef858d78 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 13:14:56 +0000 Subject: [PATCH 04/13] Fix leaf/vine removal missing canopy on tall trees, add VINE to leaf-types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related leaf-removal bugs reported for jungle trees: 1. VINE was never in the default leaf-types list, so isLeafBlock() always rejected it — vines on jungle logs/leaves were never even considered for removal. Added VINE to config.yml's leaf-types. 2. Leaf capture/discovery was centered on the single block the player broke, with a fixed radius (leaf-removal-radius). Trunk discovery has no such radius limit (BFS up to max-tree-size), so on very tall trees — giant/mega jungle trees are commonly 20-30 blocks tall — the canopy near the top can sit well outside that fixed sphere while the whole trunk still gets chopped, leaving the topmost leaves and vines untouched. executeTreeChop now derives the leaf capture center from the vertical midpoint of the whole discovered trunk (treeBlocks), and grows the radius by half the trunk's vertical span so the capture sphere always reaches from the lowest to the highest log plus the configured margin. Short trees are unaffected (span ~0, same behavior as before). The grown radius is capped at MAX_LEAF_CAPTURE_RADIUS (32) since captureLeafRegion runs synchronously on the main/region thread — protects against a pathologically tall block stack turning into an enormous scan. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VwwgSu7Yw29tb4XS8F3crn --- .../autotreechop/utils/TreeChopUtils.java | 38 ++++++++++++++++--- src/main/resources/config.yml | 1 + 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 1cd6b2a..65d7c1a 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -40,6 +40,12 @@ public class TreeChopUtils { private static final Random random = new Random(); + // Upper bound on the leaf-capture radius after growing it to cover a tall trunk (see + // executeTreeChop). captureLeafRegion scans a synchronous sphere on the main/region thread, + // so this protects against a pathologically tall "tree" (e.g. a stacked column of logs) + // turning into a multi-hundred-block synchronous scan. + private static final int MAX_LEAF_CAPTURE_RADIUS = 32; + private final AutoTreeChop plugin; private final AsyncTaskScheduler scheduler; private final BatchProcessor batchProcessor; @@ -294,17 +300,37 @@ private void executeTreeChop( // CRITICAL: Capture leaf snapshot BEFORE removing logs // This ensures we can see which logs exist for proper leaf orphan detection + // + // The capture is centered on the VERTICAL MIDPOINT of the whole discovered trunk + // (not just the block the player broke), with the radius grown to cover the trunk's + // full height plus the configured margin. A fixed radius around the break point alone + // misses canopies on very tall trees (e.g. giant/mega jungle trees, 20-30 blocks tall) + // when the player starts chopping near the base. BlockSnapshot leafSnapshot = null; + Location leafCenter = originalBlock.getLocation(); + int leafRadius = config.getLeafRemovalRadius(); if (config.isLeafRemovalEnabled()) { + int minY = Integer.MAX_VALUE; + int maxY = Integer.MIN_VALUE; + for (Location loc : treeBlocks) { + minY = Math.min(minY, loc.getBlockY()); + maxY = Math.max(maxY, loc.getBlockY()); + } + int verticalSpan = maxY - minY; + leafCenter = originalBlock.getLocation().clone(); + leafCenter.setY(minY + verticalSpan / 2.0); + leafRadius = Math.min(config.getLeafRemovalRadius() + (verticalSpan / 2), MAX_LEAF_CAPTURE_RADIUS); + try { - leafSnapshot = - BlockSnapshotCreator.captureLeafRegion(originalBlock, config.getLeafRemovalRadius(), config); + leafSnapshot = BlockSnapshotCreator.captureLeafRegion(leafCenter.getBlock(), leafRadius, config); } catch (Exception e) { plugin.getLogger().warning("Error pre-capturing leaf snapshot: " + e.getMessage()); } } BlockSnapshot finalLeafSnapshot = leafSnapshot; + Location finalLeafCenter = leafCenter; + int finalLeafRadius = leafRadius; Map logStatCounts = new HashMap<>(); boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, config); @@ -384,12 +410,12 @@ private void executeTreeChop( // Handle leaf removal if (config.isLeafRemovalEnabled() && finalLeafSnapshot != null) { long delay = config.getLeafRemovalDelayTicks(); - Location leafProcessLocation = originalBlock.getLocation(); Runnable leafTask = () -> { processLeafRemovalWithPreCapturedSnapshot( finalLeafSnapshot, - originalBlock.getLocation(), + finalLeafCenter, + finalLeafRadius, player, tool, config, @@ -398,7 +424,7 @@ private void executeTreeChop( actuallyRemovedLogs); }; - scheduler.scheduleDelayed(leafProcessLocation, leafTask, delay); + scheduler.scheduleDelayed(finalLeafCenter, leafTask, delay); } // Handle replanting @@ -439,6 +465,7 @@ private void executeTreeChop( private void processLeafRemovalWithPreCapturedSnapshot( BlockSnapshot leafSnapshot, Location centerLocation, + int radius, Player player, ItemStack tool, Config config, @@ -473,7 +500,6 @@ private void processLeafRemovalWithPreCapturedSnapshot( // Use the provided removedLogs directly // (already contains all actually removed logs from executeTreeChop) Set leavesToRemove; - int radius = config.getLeafRemovalRadius(); // Choose discovery method based on radius and mode if ("smart".equalsIgnoreCase(config.getLeafRemovalMode())) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d7d11b8..2c34a01 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -150,6 +150,7 @@ leaf-types: - PALE_OAK_LEAVES - WARPED_WART_BLOCK - NETHER_WART_BLOCK + - VINE # Replanting setting # Enable automatic sapling replanting after tree chopping From e1b0a327d7c7e238b5ecde6abe6cc1174c81293f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 09:59:46 +0000 Subject: [PATCH 05/13] Fix 2x2 sapling replant landing offset from the chopped trunk The 2x2 replant formation could be planted up to one block off on each axis from the trunk that was actually chopped. Two causes stacked: * The log recorded as the replant anchor was "the first log with the lowest Y", and a 2x2 trunk's four base logs all share that Y. The discovery set is a HashSet, so the winner was an arbitrary one of the four corners. * isLikely2x2Tree() already scanned the chopped-log set and computed the trunk's real minimum-corner anchor, but discarded it and returned only a boolean. find2x2PlantLocation() then re-guessed the anchor from that arbitrary corner, trying the origin-as-minimum-corner candidate first. With the tree gone the ground is flat, so the offset square passed the clear-and-soil check and was accepted. Dark Oak and Pale Oak skipped the footprint scan entirely, so their anchor was always a guess. Return the anchor from the footprint scan instead of a boolean and plant on it, falling back to the search only when that footprint is no longer plantable or was never detected (partial chop). Run the scan for Dark Oak and Pale Oak too, keeping their unconditional 2x2 behaviour via isAlways2x2(). Also order the anchor candidates by Y, then X, then Z, so the recorded corner is the minimum one and, above all, deterministic on the fallback path. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DLvC3xDtLN8xhL6fok7RKW --- .../autotreechop/utils/TreeChopUtils.java | 23 +++++- .../autotreechop/utils/TreeReplantUtils.java | 79 ++++++++++++++----- 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 65d7c1a..7213125 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -355,9 +355,9 @@ private void executeTreeChop( Material originalLogType = block.getType(); - // Track the lowest Y coordinate log for each type (for proper replanting) + // Track the lowest log of each type (for proper replanting) Location existingLoc = logTypesForReplant.get(originalLogType); - if (existingLoc == null || location.getBlockY() < existingLoc.getBlockY()) { + if (existingLoc == null || isLowerReplantAnchor(location, existingLoc)) { logTypesForReplant.put(originalLogType, location.clone()); } @@ -455,6 +455,25 @@ private void executeTreeChop( }); } + /** + * Orders candidate replant anchors: lowest Y first, then lowest X, then lowest Z. + * + *

Comparing Y alone is ambiguous for a 2x2 trunk, whose four base logs share the + * same Y — the winner would then be whichever log the (unordered) discovery set + * happened to yield first, making the recorded anchor a random one of the four + * corners and the replanted formation shift with it. Falling back to X and Z makes + * the anchor the minimum corner, and above all deterministic. + */ + private static boolean isLowerReplantAnchor(Location candidate, Location current) { + if (candidate.getBlockY() != current.getBlockY()) { + return candidate.getBlockY() < current.getBlockY(); + } + if (candidate.getBlockX() != current.getBlockX()) { + return candidate.getBlockX() < current.getBlockX(); + } + return candidate.getBlockZ() < current.getBlockZ(); + } + /** * Process leaf removal with PRE-CAPTURED snapshot * The snapshot was taken BEFORE logs were removed diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeReplantUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeReplantUtils.java index 17c3f03..fa73917 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeReplantUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeReplantUtils.java @@ -65,11 +65,16 @@ public static void scheduleReplant( } Location originalLocation = brokenLogBlock.getLocation().clone(); - boolean needs2x2 = isLikely2x2Tree(originalLogType, originalLocation, choppedLogs); + + // Minimum-corner anchor of the 2x2 trunk that was actually chopped, derived from the real + // footprint instead of being guessed from an arbitrary corner. Null when the tree was not a + // 2x2, or when its base was only partially removed (protection plugin, max-tree-size cut-off). + Location exactAnchor = find2x2FootprintAnchor(originalLogType, originalLocation, choppedLogs); + boolean needs2x2 = exactAnchor != null || isAlways2x2(originalLogType); Runnable replantTask = () -> { if (needs2x2) { - Location anchorLocation = find2x2PlantLocation(originalLocation, config); + Location anchorLocation = resolve2x2Anchor(exactAnchor, originalLocation, config); if (anchorLocation == null) { return; } @@ -130,30 +135,49 @@ public static void scheduleReplant( } /** - * Determines whether the chopped tree should be replanted as a 2x2 sapling - * formation. - * - *

Dark Oak and Pale Oak are always 2x2. Spruce and Jungle are 2x2 only when - * the base of the chopped tree contained four logs arranged in a 2x2 square — - * detected by scanning the chopped-log set for a matching pattern at the Y - * level of the lowest broken log. All other tree types are always single. + * Returns {@code true} for log types that are always replanted as a 2x2 + * formation regardless of what was chopped. */ - private static boolean isLikely2x2Tree(Material logType, Location lowestLogLocation, Set choppedLogs) { + private static boolean isAlways2x2(Material logType) { + XMaterial xMat = XMaterial.matchXMaterial(logType); + return xMat == XMaterial.DARK_OAK_LOG || xMat == XMaterial.PALE_OAK_LOG; + } + /** + * Returns {@code true} for log types that can form a 2x2 trunk at all. Every + * other type is always replanted as a single sapling. + */ + private static boolean canBe2x2(Material logType) { XMaterial xMat = XMaterial.matchXMaterial(logType); + return xMat == XMaterial.DARK_OAK_LOG + || xMat == XMaterial.PALE_OAK_LOG + || xMat == XMaterial.SPRUCE_LOG + || xMat == XMaterial.JUNGLE_LOG; + } - // Dark Oak and Pale Oak are always planted as 2x2 - if (xMat == XMaterial.DARK_OAK_LOG || xMat == XMaterial.PALE_OAK_LOG) { - return true; - } + /** + * Finds the exact minimum-corner anchor of the 2x2 trunk that was chopped, by + * scanning the chopped-log set for four logs forming a square at the Y level of + * the lowest broken log. + * + *

The anchor is unique: for a given 2x2 footprint only its minimum corner + * makes all four positions match. Returning the anchor rather than a boolean is + * what keeps the replanted saplings aligned with the trunk that was removed — + * {@code lowestLogLocation} may be any of the four corners, so deriving the + * formation from it by assuming it is the minimum corner shifts the saplings by + * up to one block on each axis. + * + *

Returns null when the tree is not a 2x2 type, or when its base was not + * fully chopped (a protection plugin denied a block, or the max-tree-size limit + * was hit), in which case callers fall back to searching for a suitable spot. + */ + private static Location find2x2FootprintAnchor( + Material logType, Location lowestLogLocation, Set choppedLogs) { - // Only Spruce and Jungle can be big (2x2) trees — everything else is always single - if (xMat != XMaterial.SPRUCE_LOG && xMat != XMaterial.JUNGLE_LOG) { - return false; + if (!canBe2x2(logType)) { + return null; } - // Detect 2x2 by checking whether four logs of this type form a square at - // the base Y level among the actually-chopped blocks. int baseY = lowestLogLocation.getBlockY(); int baseX = lowestLogLocation.getBlockX(); int baseZ = lowestLogLocation.getBlockZ(); @@ -172,11 +196,24 @@ private static boolean isLikely2x2Tree(Material logType, Location lowestLogLocat } } if (all4Present) { - return true; + return new Location(world, ax, baseY, az); } } - return false; + return null; + } + + /** + * Picks the anchor to plant the 2x2 formation at, preferring the exact footprint + * of the chopped trunk and only searching for an alternative when that footprint + * is no longer plantable (uneven ground, a block placed in the meantime, or no + * footprint detected at all). + */ + private static Location resolve2x2Anchor(Location exactAnchor, Location originalLocation, Config config) { + if (exactAnchor != null && is2x2FormationValid(exactAnchor.getBlock(), config)) { + return exactAnchor; + } + return find2x2PlantLocation(originalLocation, config); } /** From 62a0daab2a3d61870d90f59a5cf7e596ed58b8f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 11:15:29 +0000 Subject: [PATCH 06/13] Add a per-player auto pickup toggle command Auto pickup was an all-or-nothing server setting: once enable-auto-pickup was on, every player with the permission had chopped drops forced into their inventory. Players who want the drops on the ground had no way out. Add /atc autopickup (alias /atc pickup) as a self-only toggle, mirroring the existing /atc toggle. The choice is stored per player in the player_data table so it survives relogs and restarts, and is refused when auto pickup is off server-wide so nobody stores an "on" that does nothing. The new autoPickupEnabled column is added to tables created by older versions via a JDBC-metadata check plus ALTER TABLE, defaulting existing players to on so behaviour does not change under them. New players follow the new defaultAutoPickup config key. --- README.md | 3 +- .../milkteamc/autotreechop/AutoTreeChop.java | 9 +- .../org/milkteamc/autotreechop/Config.java | 6 + .../milkteamc/autotreechop/MessageKeys.java | 4 + .../milkteamc/autotreechop/PlayerConfig.java | 11 ++ .../autotreechop/command/ToggleCommand.java | 28 +++++ .../database/DatabaseManager.java | 112 ++++++++++++++---- .../events/PlayerJoinListener.java | 12 +- .../utils/DropCollectionUtils.java | 12 +- .../autotreechop/utils/TreeChopUtils.java | 4 +- src/main/resources/config.yml | 3 + src/main/resources/lang/de.properties | 4 + src/main/resources/lang/en.properties | 4 + src/main/resources/lang/es.properties | 4 + src/main/resources/lang/fr.properties | 4 + src/main/resources/lang/it.properties | 4 + src/main/resources/lang/ja.properties | 6 +- src/main/resources/lang/ms.properties | 6 +- src/main/resources/lang/ru.properties | 4 + src/main/resources/lang/tr.properties | 4 + src/main/resources/lang/zh.properties | 4 + 21 files changed, 214 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 4f9dfd8..8890cad 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo |--------|-------------| | `/atc` | Toggle AutoTreeChop | | `/atc confirm` | Confirm a pending chop (idle / no-leaves warning) | +| `/atc autopickup` | Toggle auto pickup of chopped drops for yourself | | `/atc usage` | Show daily usage | | `/atc reload` | Reload plugin config | | `/atc toggle ` | Toggle for another player | @@ -93,7 +94,7 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo | `autotreechop.updatechecker` | Receive update notifications | OP | | `autotreechop.replant` | Enable auto replanting | Everyone | | `autotreechop.leaves` | Enable leaves removal | Everyone | -| `autotreechop.autopickup` | Collect chopped drops straight into the inventory | Everyone | +| `autotreechop.autopickup` | Collect chopped drops straight into the inventory, and use `/atc autopickup` | Everyone | --- diff --git a/src/main/java/org/milkteamc/autotreechop/AutoTreeChop.java b/src/main/java/org/milkteamc/autotreechop/AutoTreeChop.java index b9849d3..c6a8cb8 100644 --- a/src/main/java/org/milkteamc/autotreechop/AutoTreeChop.java +++ b/src/main/java/org/milkteamc/autotreechop/AutoTreeChop.java @@ -302,7 +302,7 @@ public PlayerConfig getPlayerConfig(UUID playerUUID) { getLogger().warning("PlayerConfig not found for " + playerUUID + ", loading synchronously"); try { DatabaseManager.PlayerData data = databaseManager - .loadPlayerDataAsync(playerUUID, config.getDefaultTreeChop()) + .loadPlayerDataAsync(playerUUID, config.getDefaultTreeChop(), config.getDefaultAutoPickup()) .get(); playerConfig = new PlayerConfig(playerUUID, data); @@ -310,7 +310,12 @@ public PlayerConfig getPlayerConfig(UUID playerUUID) { } catch (Exception e) { getLogger().warning("Failed to load player data: " + e.getMessage()); DatabaseManager.PlayerData defaultData = new DatabaseManager.PlayerData( - playerUUID, config.getDefaultTreeChop(), 0, 0, java.time.LocalDate.now()); + playerUUID, + config.getDefaultTreeChop(), + config.getDefaultAutoPickup(), + 0, + 0, + java.time.LocalDate.now()); playerConfig = new PlayerConfig(playerUUID, defaultData); playerConfigs.put(playerUUID, playerConfig); } diff --git a/src/main/java/org/milkteamc/autotreechop/Config.java b/src/main/java/org/milkteamc/autotreechop/Config.java index 5604539..94bac52 100644 --- a/src/main/java/org/milkteamc/autotreechop/Config.java +++ b/src/main/java/org/milkteamc/autotreechop/Config.java @@ -106,6 +106,7 @@ public class Config { private boolean callBlockBreakEvent; private boolean incrementBlockStatistics; private boolean autoPickupEnabled; + private boolean defaultAutoPickup; public Config(AutoTreeChop plugin) { this.plugin = plugin; @@ -231,6 +232,7 @@ private void loadValues() { callBlockBreakEvent = config.getBoolean("call-block-break-event", true); incrementBlockStatistics = config.getBoolean("increment-block-statistics", false); autoPickupEnabled = config.getBoolean("enable-auto-pickup", false); + defaultAutoPickup = config.getBoolean("defaultAutoPickup", true); autoReplantEnabled = config.getBoolean("enable-auto-replant", true); replantDelayTicks = config.getLong("replant-delay-ticks", 15L); @@ -549,6 +551,10 @@ public boolean isAutoPickupEnabled() { return autoPickupEnabled; } + public boolean getDefaultAutoPickup() { + return defaultAutoPickup; + } + public int getIdleTimeoutSeconds() { return idleTimeoutSeconds; } diff --git a/src/main/java/org/milkteamc/autotreechop/MessageKeys.java b/src/main/java/org/milkteamc/autotreechop/MessageKeys.java index 44ad32c..de1723c 100644 --- a/src/main/java/org/milkteamc/autotreechop/MessageKeys.java +++ b/src/main/java/org/milkteamc/autotreechop/MessageKeys.java @@ -43,6 +43,10 @@ private MessageKeys() {} public static final String CONFIRMATION_REQUIRED_BOTH = "confirmationRequiredBoth"; public static final String CONFIRMATION_SUCCESS = "confirmationSuccess"; public static final String NO_PENDING_CONFIRMATION = "noPendingConfirmation"; + public static final String AUTO_PICKUP_ENABLED = "autoPickupEnabled"; + public static final String AUTO_PICKUP_DISABLED = "autoPickupDisabled"; + public static final String AUTO_PICKUP_UNAVAILABLE = "autoPickupUnavailable"; + public static final String ALREADY_ENABLED = "alreadyEnabled"; public static final String ALREADY_DISABLED = "alreadyDisabled"; public static final String INVENTORY_FULL = "inventoryFull"; diff --git a/src/main/java/org/milkteamc/autotreechop/PlayerConfig.java b/src/main/java/org/milkteamc/autotreechop/PlayerConfig.java index 25808c3..1d0d3e4 100644 --- a/src/main/java/org/milkteamc/autotreechop/PlayerConfig.java +++ b/src/main/java/org/milkteamc/autotreechop/PlayerConfig.java @@ -52,6 +52,17 @@ public void setAutoTreeChopEnabled(boolean enabled) { } } + public boolean isAutoPickupEnabled() { + return data.isAutoPickupEnabled(); + } + + public void setAutoPickupEnabled(boolean enabled) { + if (data.isAutoPickupEnabled() != enabled) { + data.setAutoPickupEnabled(enabled); + markDirty(); + } + } + public int getDailyUses() { checkAndUpdateDate(); return data.getDailyUses(); diff --git a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java index a564e55..0d2e850 100644 --- a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java +++ b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java @@ -182,6 +182,34 @@ public void disable(BukkitCommandActor actor, EntitySelector targetPlaye } } + /** + * Flips the player's own auto pickup preference. The preference is stored per player and + * persists across sessions, exactly like the AutoTreeChop toggle above. + * + *

Toggling is refused when auto pickup cannot take effect anyway — the server disabled it + * in config.yml, or the player lacks {@code autotreechop.autopickup} — so a player never ends + * up with a stored "on" that silently does nothing. + */ + @Subcommand({"autopickup", "pickup"}) + @CommandPermission("autotreechop.autopickup") + public void autoPickup(BukkitCommandActor actor) { + if (!(actor.sender() instanceof Player player)) { + AutoTreeChop.sendMessage(actor.sender(), MessageKeys.ONLY_PLAYERS); + return; + } + + if (!plugin.getPluginConfig().isAutoPickupEnabled()) { + AutoTreeChop.sendMessage(player, MessageKeys.AUTO_PICKUP_UNAVAILABLE); + return; + } + + PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId()); + boolean enabled = !playerConfig.isAutoPickupEnabled(); + playerConfig.setAutoPickupEnabled(enabled); + + AutoTreeChop.sendMessage(player, enabled ? MessageKeys.AUTO_PICKUP_ENABLED : MessageKeys.AUTO_PICKUP_DISABLED); + } + private void performSelfToggle(BukkitCommandActor actor) { if (!(actor.sender() instanceof Player player)) { AutoTreeChop.sendMessage(actor.sender(), MessageKeys.ONLY_PLAYERS); diff --git a/src/main/java/org/milkteamc/autotreechop/database/DatabaseManager.java b/src/main/java/org/milkteamc/autotreechop/database/DatabaseManager.java index 9b71380..5e91db7 100644 --- a/src/main/java/org/milkteamc/autotreechop/database/DatabaseManager.java +++ b/src/main/java/org/milkteamc/autotreechop/database/DatabaseManager.java @@ -73,6 +73,7 @@ private void createTable() { PreparedStatement stmt = conn.prepareStatement( "CREATE TABLE IF NOT EXISTS player_data (" + "uuid VARCHAR(36) PRIMARY KEY," + "autoTreeChopEnabled BOOLEAN," + + "autoPickupEnabled BOOLEAN," + "dailyUses INT," + "dailyBlocksBroken INT," + "lastUseDate VARCHAR(10))")) { @@ -80,9 +81,57 @@ private void createTable() { } catch (SQLException e) { plugin.getLogger().warning("Error creating database table: " + e.getMessage()); } + + addMissingColumns(); + } + + /** + * Adds columns introduced after the first release to tables created by an older version. + * + *

{@code CREATE TABLE IF NOT EXISTS} leaves an existing table untouched, so a server that + * has been running since before auto pickup became a per-player setting would keep a table + * without the {@code autoPickupEnabled} column and every read/write of it would fail. The + * column list is read from JDBC metadata, which both SQLite and MySQL support, instead of + * relying on dialect-specific "ADD COLUMN IF NOT EXISTS" syntax. + */ + private void addMissingColumns() { + try (Connection conn = dataSource.getConnection()) { + if (hasColumn(conn, "autoPickupEnabled")) { + return; + } + + try (PreparedStatement stmt = + conn.prepareStatement("ALTER TABLE player_data ADD COLUMN autoPickupEnabled BOOLEAN")) { + stmt.executeUpdate(); + } + + // Existing players keep auto pickup on; the global config flag and the + // autotreechop.autopickup permission still decide whether it does anything. + try (PreparedStatement stmt = conn.prepareStatement( + "UPDATE player_data SET autoPickupEnabled = ? WHERE autoPickupEnabled IS NULL")) { + stmt.setBoolean(1, true); + stmt.executeUpdate(); + } + + plugin.getLogger().info("Added autoPickupEnabled column to player_data."); + } catch (SQLException e) { + plugin.getLogger().warning("Error migrating database table: " + e.getMessage()); + } + } + + private boolean hasColumn(Connection conn, String columnName) throws SQLException { + try (ResultSet rs = conn.getMetaData().getColumns(null, null, "player_data", null)) { + while (rs.next()) { + if (columnName.equalsIgnoreCase(rs.getString("COLUMN_NAME"))) { + return true; + } + } + } + return false; } - public CompletableFuture loadPlayerDataAsync(UUID playerUUID, boolean defaultTreeChop) { + public CompletableFuture loadPlayerDataAsync( + UUID playerUUID, boolean defaultTreeChop, boolean defaultAutoPickup) { return CompletableFuture.supplyAsync(() -> { try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement("SELECT * FROM player_data WHERE uuid = ?")) { @@ -91,35 +140,43 @@ public CompletableFuture loadPlayerDataAsync(UUID playerUUID, boolea ResultSet rs = stmt.executeQuery(); if (rs.next()) { + boolean autoPickupEnabled = rs.getBoolean("autoPickupEnabled"); + if (rs.wasNull()) { + autoPickupEnabled = defaultAutoPickup; + } + return new PlayerData( playerUUID, rs.getBoolean("autoTreeChopEnabled"), + autoPickupEnabled, rs.getInt("dailyUses"), rs.getInt("dailyBlocksBroken"), LocalDate.parse(rs.getString("lastUseDate"))); } else { - PlayerData data = new PlayerData(playerUUID, defaultTreeChop, 0, 0, LocalDate.now()); + PlayerData data = + new PlayerData(playerUUID, defaultTreeChop, defaultAutoPickup, 0, 0, LocalDate.now()); insertPlayerData(data); return data; } } catch (SQLException e) { plugin.getLogger().warning("Error loading player data: " + e.getMessage()); - return new PlayerData(playerUUID, defaultTreeChop, 0, 0, LocalDate.now()); + return new PlayerData(playerUUID, defaultTreeChop, defaultAutoPickup, 0, 0, LocalDate.now()); } }); } public void savePlayerDataSync(PlayerData data) { try (Connection conn = dataSource.getConnection(); - PreparedStatement stmt = - conn.prepareStatement("UPDATE player_data SET autoTreeChopEnabled = ?, dailyUses = ?, " + PreparedStatement stmt = conn.prepareStatement( + "UPDATE player_data SET autoTreeChopEnabled = ?, autoPickupEnabled = ?, dailyUses = ?, " + "dailyBlocksBroken = ?, lastUseDate = ? WHERE uuid = ?")) { stmt.setBoolean(1, data.isAutoTreeChopEnabled()); - stmt.setInt(2, data.getDailyUses()); - stmt.setInt(3, data.getDailyBlocksBroken()); - stmt.setString(4, data.getLastUseDate().toString()); - stmt.setString(5, data.getPlayerUUID().toString()); + stmt.setBoolean(2, data.isAutoPickupEnabled()); + stmt.setInt(3, data.getDailyUses()); + stmt.setInt(4, data.getDailyBlocksBroken()); + stmt.setString(5, data.getLastUseDate().toString()); + stmt.setString(6, data.getPlayerUUID().toString()); int rows = stmt.executeUpdate(); if (rows == 0) { @@ -137,16 +194,17 @@ public CompletableFuture savePlayerDataBatchAsync(Map da try (Connection conn = dataSource.getConnection()) { conn.setAutoCommit(false); - try (PreparedStatement stmt = - conn.prepareStatement("UPDATE player_data SET autoTreeChopEnabled = ?, dailyUses = ?, " + try (PreparedStatement stmt = conn.prepareStatement( + "UPDATE player_data SET autoTreeChopEnabled = ?, autoPickupEnabled = ?, dailyUses = ?, " + "dailyBlocksBroken = ?, lastUseDate = ? WHERE uuid = ?")) { for (PlayerData data : dataMap.values()) { stmt.setBoolean(1, data.isAutoTreeChopEnabled()); - stmt.setInt(2, data.getDailyUses()); - stmt.setInt(3, data.getDailyBlocksBroken()); - stmt.setString(4, data.getLastUseDate().toString()); - stmt.setString(5, data.getPlayerUUID().toString()); + stmt.setBoolean(2, data.isAutoPickupEnabled()); + stmt.setInt(3, data.getDailyUses()); + stmt.setInt(4, data.getDailyBlocksBroken()); + stmt.setString(5, data.getLastUseDate().toString()); + stmt.setString(6, data.getPlayerUUID().toString()); stmt.addBatch(); } @@ -164,15 +222,16 @@ public CompletableFuture savePlayerDataBatchAsync(Map da private void insertPlayerData(PlayerData data) throws SQLException { try (Connection conn = dataSource.getConnection(); - PreparedStatement stmt = - conn.prepareStatement("INSERT INTO player_data (uuid, autoTreeChopEnabled, dailyUses, " - + "dailyBlocksBroken, lastUseDate) VALUES (?, ?, ?, ?, ?)")) { + PreparedStatement stmt = conn.prepareStatement( + "INSERT INTO player_data (uuid, autoTreeChopEnabled, autoPickupEnabled, dailyUses, " + + "dailyBlocksBroken, lastUseDate) VALUES (?, ?, ?, ?, ?, ?)")) { stmt.setString(1, data.getPlayerUUID().toString()); stmt.setBoolean(2, data.isAutoTreeChopEnabled()); - stmt.setInt(3, data.getDailyUses()); - stmt.setInt(4, data.getDailyBlocksBroken()); - stmt.setString(5, data.getLastUseDate().toString()); + stmt.setBoolean(3, data.isAutoPickupEnabled()); + stmt.setInt(4, data.getDailyUses()); + stmt.setInt(5, data.getDailyBlocksBroken()); + stmt.setString(6, data.getLastUseDate().toString()); stmt.executeUpdate(); } } @@ -186,6 +245,7 @@ public void close() { public static class PlayerData { private final UUID playerUUID; private boolean autoTreeChopEnabled; + private boolean autoPickupEnabled; private int dailyUses; private int dailyBlocksBroken; private LocalDate lastUseDate; @@ -193,11 +253,13 @@ public static class PlayerData { public PlayerData( UUID playerUUID, boolean autoTreeChopEnabled, + boolean autoPickupEnabled, int dailyUses, int dailyBlocksBroken, LocalDate lastUseDate) { this.playerUUID = playerUUID; this.autoTreeChopEnabled = autoTreeChopEnabled; + this.autoPickupEnabled = autoPickupEnabled; this.dailyUses = dailyUses; this.dailyBlocksBroken = dailyBlocksBroken; this.lastUseDate = lastUseDate; @@ -215,6 +277,14 @@ public void setAutoTreeChopEnabled(boolean enabled) { this.autoTreeChopEnabled = enabled; } + public boolean isAutoPickupEnabled() { + return autoPickupEnabled; + } + + public void setAutoPickupEnabled(boolean enabled) { + this.autoPickupEnabled = enabled; + } + public int getDailyUses() { return dailyUses; } diff --git a/src/main/java/org/milkteamc/autotreechop/events/PlayerJoinListener.java b/src/main/java/org/milkteamc/autotreechop/events/PlayerJoinListener.java index 8ccb67c..631260b 100644 --- a/src/main/java/org/milkteamc/autotreechop/events/PlayerJoinListener.java +++ b/src/main/java/org/milkteamc/autotreechop/events/PlayerJoinListener.java @@ -40,7 +40,10 @@ public void onPlayerJoin(PlayerJoinEvent event) { UUID playerUUID = player.getUniqueId(); plugin.getDatabaseManager() - .loadPlayerDataAsync(playerUUID, plugin.getPluginConfig().getDefaultTreeChop()) + .loadPlayerDataAsync( + playerUUID, + plugin.getPluginConfig().getDefaultTreeChop(), + plugin.getPluginConfig().getDefaultAutoPickup()) .thenAccept(data -> { PlayerConfig playerConfig = new PlayerConfig(playerUUID, data); plugin.getAllPlayerConfigs().put(playerUUID, playerConfig); @@ -55,7 +58,12 @@ public void onPlayerJoin(PlayerJoinEvent event) { plugin.getLogger() .warning("Failed to load data for player " + player.getName() + ": " + ex.getMessage()); DatabaseManager.PlayerData defaultData = new DatabaseManager.PlayerData( - playerUUID, plugin.getPluginConfig().getDefaultTreeChop(), 0, 0, java.time.LocalDate.now()); + playerUUID, + plugin.getPluginConfig().getDefaultTreeChop(), + plugin.getPluginConfig().getDefaultAutoPickup(), + 0, + 0, + java.time.LocalDate.now()); PlayerConfig fallback = new PlayerConfig(playerUUID, defaultData); plugin.getAllPlayerConfigs().put(playerUUID, fallback); // Default is disabled, so no markRejoin needed here. diff --git a/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java index 5204935..aa703ac 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java @@ -31,6 +31,7 @@ import org.milkteamc.autotreechop.AutoTreeChop; import org.milkteamc.autotreechop.Config; import org.milkteamc.autotreechop.MessageKeys; +import org.milkteamc.autotreechop.PlayerConfig; /** * Collects the drops of blocks broken by AutoTreeChop and hands them straight to the player @@ -52,11 +53,14 @@ public final class DropCollectionUtils { private DropCollectionUtils() {} /** - * Auto pickup requires both the config flag and the permission node, following the same - * pattern as {@link TreeReplantUtils#isReplantEnabledForPlayer(Player, Config)}. + * Auto pickup requires the config flag, the permission node and the player's own toggle + * (persisted per player, flipped with {@code /atc autopickup}), following the same pattern as + * {@link TreeReplantUtils#isReplantEnabledForPlayer(Player, Config)}. */ - public static boolean isAutoPickupEnabledForPlayer(Player player, Config config) { - return config.isAutoPickupEnabled() && player.hasPermission("autotreechop.autopickup"); + public static boolean isAutoPickupEnabledForPlayer(Player player, PlayerConfig playerConfig, Config config) { + return config.isAutoPickupEnabled() + && player.hasPermission("autotreechop.autopickup") + && playerConfig.isAutoPickupEnabled(); } /** diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 7213125..d5ed76f 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -333,7 +333,7 @@ private void executeTreeChop( int finalLeafRadius = leafRadius; Map logStatCounts = new HashMap<>(); - boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, config); + boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, playerConfig, config); List collectedDrops = new ArrayList<>(); batchProcessor.processBatch( @@ -573,7 +573,7 @@ private void executeLeafRemoval( List leafList = new ArrayList<>(leavesToRemove); int batchSize = config.getLeafRemovalBatchSize(); Map leafStatCounts = new HashMap<>(); - boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, config); + boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, playerConfig, config); List collectedDrops = new ArrayList<>(); batchProcessor.processBatchWithTermination( diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2c34a01..f13a3e4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -90,7 +90,10 @@ increment-block-statistics: false # Respects the tool used and its enchantments (Fortune, Silk Touch), just like a normal block break. # Items that do not fit are dropped at the player's feet. # Also requires the "autotreechop.autopickup" permission. +# Players can turn it off for themselves with "/atc autopickup"; that choice is saved per player. enable-auto-pickup: false +# Set the auto pickup state new players start with (only matters when enable-auto-pickup is true). +defaultAutoPickup: true # Protection plugins setting # If you are using Residence, you can set which Flag players have access to AutoTreeChop in residence. diff --git a/src/main/resources/lang/de.properties b/src/main/resources/lang/de.properties index 898888c..46c3fe0 100644 --- a/src/main/resources/lang/de.properties +++ b/src/main/resources/lang/de.properties @@ -24,3 +24,7 @@ aboutHeader=AutoTreeChop - v{version}Lizenz: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop + +autoPickupEnabled=Automatisches Aufsammeln aktiviert. Gefällte Gegenstände landen direkt im Inventar. +autoPickupDisabled=Automatisches Aufsammeln deaktiviert. Gefällte Gegenstände fallen auf den Boden. +autoPickupUnavailable=Automatisches Aufsammeln ist auf diesem Server deaktiviert. diff --git a/src/main/resources/lang/en.properties b/src/main/resources/lang/en.properties index 3c01bef..6f9f52e 100644 --- a/src/main/resources/lang/en.properties +++ b/src/main/resources/lang/en.properties @@ -41,3 +41,7 @@ aboutHeader=AutoTreeChop - v{version}License: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop + +autoPickupEnabled=Auto pickup enabled. Chopped items now go straight into your inventory. +autoPickupDisabled=Auto pickup disabled. Chopped items now drop on the ground. +autoPickupUnavailable=Auto pickup is disabled on this server. diff --git a/src/main/resources/lang/es.properties b/src/main/resources/lang/es.properties index 3563171..5fd1185 100644 --- a/src/main/resources/lang/es.properties +++ b/src/main/resources/lang/es.properties @@ -26,3 +26,7 @@ aboutGithub=GitHub: https://github.com/milkteamc/autotreechopModrinth: https://modrinth.com/plugin/autotreechop alreadyEnabled=AutoTreeChop está activado. alreadyDisabled=AutoTreeChop está desactivado. + +autoPickupEnabled=Recogida automática activada. Los objetos talados van directo a tu inventario. +autoPickupDisabled=Recogida automática desactivada. Los objetos talados caerán al suelo. +autoPickupUnavailable=La recogida automática está desactivada en este servidor. diff --git a/src/main/resources/lang/fr.properties b/src/main/resources/lang/fr.properties index 560d27f..2876771 100644 --- a/src/main/resources/lang/fr.properties +++ b/src/main/resources/lang/fr.properties @@ -24,3 +24,7 @@ aboutGithub=GitHub: https://github.com/milkteamc/autotreechopModrinth: https://modrinth.com/plugin/autotreechop confirmationRequiredNoLeaves=Pas de feuillage détecté à proximité. Cette bûche a pu être placée par un joueur. Couper une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. confirmationRequiredBoth=AutoTreeChop n'a pas été récemment utilisé et aucun feuillage n'a été détecté à proximité. Coupez une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. + +autoPickupEnabled=Ramassage automatique activé. Les objets coupés vont directement dans votre inventaire. +autoPickupDisabled=Ramassage automatique désactivé. Les objets coupés tombent au sol. +autoPickupUnavailable=Le ramassage automatique est désactivé sur ce serveur. diff --git a/src/main/resources/lang/it.properties b/src/main/resources/lang/it.properties index 8dfdd28..b4844c7 100644 --- a/src/main/resources/lang/it.properties +++ b/src/main/resources/lang/it.properties @@ -24,3 +24,7 @@ aboutHeader=AutoTreeChop - v{version}Licenza: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop + +autoPickupEnabled=Raccolta automatica attivata. Gli oggetti tagliati vanno direttamente nell'inventario. +autoPickupDisabled=Raccolta automatica disattivata. Gli oggetti tagliati cadranno a terra. +autoPickupUnavailable=La raccolta automatica è disattivata su questo server. diff --git a/src/main/resources/lang/ja.properties b/src/main/resources/lang/ja.properties index 7cd2c2b..464961a 100644 --- a/src/main/resources/lang/ja.properties +++ b/src/main/resources/lang/ja.properties @@ -11,4 +11,8 @@ hitmaxusage=自動伐採の1日の使用上限に達しまし no-permission=その権限がありません。 usage=今日は自動伐採を{current_uses}/{max_uses}回使用しました。 noResidencePermissions=ここでは自動伐採を使用する権限がありません。 -stillInCooldown=まだクールダウン中です!{cooldown_time}秒後にもう一度お試しください。 \ No newline at end of file +stillInCooldown=まだクールダウン中です!{cooldown_time}秒後にもう一度お試しください。 + +autoPickupEnabled=自動回収を有効にしました。伐採したアイテムは直接インベントリに入ります。 +autoPickupDisabled=自動回収を無効にしました。伐採したアイテムは地面にドロップします。 +autoPickupUnavailable=このサーバーでは自動回収が無効になっています。 diff --git a/src/main/resources/lang/ms.properties b/src/main/resources/lang/ms.properties index 0c542e4..596ed4e 100644 --- a/src/main/resources/lang/ms.properties +++ b/src/main/resources/lang/ms.properties @@ -14,4 +14,8 @@ disabledForOther=Fungsi Penebangan pokok ditutup kepada {player stillInCooldown=Anda masih dalam tempoh bertenang! Cuba lagi selepas {cooldown_time} saat. consoleName=konsol sneakEnabled=Fungsi Penebangan pokok automatik diaktifkan semasa mencangkung. -sneakDisabled=Fungsi Penebangan pokok automatik ditutup semasa mencangkung. \ No newline at end of file +sneakDisabled=Fungsi Penebangan pokok automatik ditutup semasa mencangkung. + +autoPickupEnabled=Kutipan automatik dihidupkan. Item yang ditebang terus masuk ke inventori anda. +autoPickupDisabled=Kutipan automatik dimatikan. Item yang ditebang akan jatuh ke tanah. +autoPickupUnavailable=Kutipan automatik dilumpuhkan di pelayan ini. diff --git a/src/main/resources/lang/ru.properties b/src/main/resources/lang/ru.properties index 3a83c39..1a01e90 100644 --- a/src/main/resources/lang/ru.properties +++ b/src/main/resources/lang/ru.properties @@ -15,3 +15,7 @@ stillInCooldown=Перезарядка авторубки. От only-players=Эта команда может быть использована только игроками. sneakEnabled=Автоматическая рубка деревьев включается тайком. sneakDisabled=Автоматическая рубка деревьев отключена путем остановки подкрадывания. + +autoPickupEnabled=Автоподбор включён. Срубленные предметы попадают прямо в инвентарь. +autoPickupDisabled=Автоподбор выключен. Срубленные предметы будут падать на землю. +autoPickupUnavailable=Автоподбор отключён на этом сервере. diff --git a/src/main/resources/lang/tr.properties b/src/main/resources/lang/tr.properties index d1d0f47..79e12c3 100644 --- a/src/main/resources/lang/tr.properties +++ b/src/main/resources/lang/tr.properties @@ -24,3 +24,7 @@ aboutHeader=AutoTreeChop - v{version}Lisans: GNU Genel Kamu Lisansı v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth Sitesi: https://modrinth.com/plugin/autotreechop + +autoPickupEnabled=Otomatik toplama açıldı. Kesilen eşyalar doğrudan envanterine gider. +autoPickupDisabled=Otomatik toplama kapatıldı. Kesilen eşyalar yere düşer. +autoPickupUnavailable=Bu sunucuda otomatik toplama devre dışı. diff --git a/src/main/resources/lang/zh.properties b/src/main/resources/lang/zh.properties index f846214..75eede3 100644 --- a/src/main/resources/lang/zh.properties +++ b/src/main/resources/lang/zh.properties @@ -41,3 +41,7 @@ aboutHeader=AutoTreeChop - v{version}授權條款:GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub:https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth:https://modrinth.com/plugin/autotreechop + +autoPickupEnabled=自動收集已啟用,砍下的物品會直接進入背包。 +autoPickupDisabled=自動收集已停用,砍下的物品會掉落在地上。 +autoPickupUnavailable=本伺服器已停用自動收集功能。 From 00dacc5dfceaaaa66920017e91772b4c9470a974 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 11:50:10 +0000 Subject: [PATCH 07/13] Show a refusal instead of hiding /atc autopickup without permission Guarding the subcommand with autotreechop.autopickup made Lamp drop it from tab completion and answer "unknown command" for anyone missing the node, so a player whose permission had been revoked could not tell the feature apart from one that does not exist. Guard on autotreechop.use like the other self-service subcommands and check the auto pickup node in the body, so the refusal is an explicit no-permission message. --- README.md | 4 ++-- .../autotreechop/command/ToggleCommand.java | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8890cad..65f1a5f 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,14 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo | Permission | Description | Default | |------------|-------------|-------------| -| `autotreechop.use` | Use `/atc`, `/atc confirm`, and `/atc usage` commands | Everyone | +| `autotreechop.use` | Use `/atc`, `/atc confirm`, `/atc usage`, and `/atc autopickup` commands | Everyone | | `autotreechop.vip` | Ignore usage limits | OP | | `autotreechop.other` | Toggle others' ATC status | OP | | `autotreechop.reload` | Reload config file | OP | | `autotreechop.updatechecker` | Receive update notifications | OP | | `autotreechop.replant` | Enable auto replanting | Everyone | | `autotreechop.leaves` | Enable leaves removal | Everyone | -| `autotreechop.autopickup` | Collect chopped drops straight into the inventory, and use `/atc autopickup` | Everyone | +| `autotreechop.autopickup` | Collect chopped drops straight into the inventory | Everyone | --- diff --git a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java index 0d2e850..a147935 100644 --- a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java +++ b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java @@ -186,18 +186,28 @@ public void disable(BukkitCommandActor actor, EntitySelector targetPlaye * Flips the player's own auto pickup preference. The preference is stored per player and * persists across sessions, exactly like the AutoTreeChop toggle above. * - *

Toggling is refused when auto pickup cannot take effect anyway — the server disabled it - * in config.yml, or the player lacks {@code autotreechop.autopickup} — so a player never ends - * up with a stored "on" that silently does nothing. + *

Gated on {@code autotreechop.use} rather than {@code autotreechop.autopickup} on purpose: + * a player without the auto pickup permission should be told they may not use the feature, + * not have the subcommand disappear from tab completion and answer "unknown command". + * The real permission is checked in the body so the refusal is an explicit message. + * + *

Toggling is also refused when auto pickup cannot take effect anyway — the server + * disabled it in config.yml — so a player never ends up with a stored "on" that silently + * does nothing. */ @Subcommand({"autopickup", "pickup"}) - @CommandPermission("autotreechop.autopickup") + @CommandPermission("autotreechop.use") public void autoPickup(BukkitCommandActor actor) { if (!(actor.sender() instanceof Player player)) { AutoTreeChop.sendMessage(actor.sender(), MessageKeys.ONLY_PLAYERS); return; } + if (!player.hasPermission("autotreechop.autopickup")) { + AutoTreeChop.sendMessage(player, MessageKeys.NO_PERMISSION); + return; + } + if (!plugin.getPluginConfig().isAutoPickupEnabled()) { AutoTreeChop.sendMessage(player, MessageKeys.AUTO_PICKUP_UNAVAILABLE); return; From 0bc6077dd394a23a87365438b3a77a74c9830bf2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 11:52:49 +0000 Subject: [PATCH 08/13] Check the server-wide auto pickup flag before the player's permission When enable-auto-pickup is false the feature is off for everyone, so answering "you do not have permission" pointed the player at a permission node that would not have helped. Check the config flag first and report that the server has auto pickup disabled. --- .../autotreechop/command/ToggleCommand.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java index a147935..5ad1e8c 100644 --- a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java +++ b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java @@ -193,11 +193,18 @@ public void disable(BukkitCommandActor actor, EntitySelector targetPlaye * *

Toggling is also refused when auto pickup cannot take effect anyway — the server * disabled it in config.yml — so a player never ends up with a stored "on" that silently - * does nothing. + * does nothing. That server-wide state is checked first: when the feature is off for + * everyone, "auto pickup is disabled on this server" is the honest answer, and telling a + * player they lack the permission would point them at the wrong thing. */ @Subcommand({"autopickup", "pickup"}) @CommandPermission("autotreechop.use") public void autoPickup(BukkitCommandActor actor) { + if (!plugin.getPluginConfig().isAutoPickupEnabled()) { + AutoTreeChop.sendMessage(actor.sender(), MessageKeys.AUTO_PICKUP_UNAVAILABLE); + return; + } + if (!(actor.sender() instanceof Player player)) { AutoTreeChop.sendMessage(actor.sender(), MessageKeys.ONLY_PLAYERS); return; @@ -208,11 +215,6 @@ public void autoPickup(BukkitCommandActor actor) { return; } - if (!plugin.getPluginConfig().isAutoPickupEnabled()) { - AutoTreeChop.sendMessage(player, MessageKeys.AUTO_PICKUP_UNAVAILABLE); - return; - } - PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId()); boolean enabled = !playerConfig.isAutoPickupEnabled(); playerConfig.setAutoPickupEnabled(enabled); From 626e181b144252d25ce590bbf0fb2b3e95089d96 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 11:53:30 +0000 Subject: [PATCH 09/13] Stop granting autotreechop.autopickup to every player by default Auto pickup is a paid feature, so defaulting the node to true handed it to everyone as soon as a server turned enable-auto-pickup on. Default it to op, the same tier autotreechop.vip already uses, so it has to be granted to the ranks that paid for it. --- README.md | 2 +- src/main/resources/config.yml | 3 ++- src/main/resources/plugin.yml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 65f1a5f..f128706 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo | `autotreechop.updatechecker` | Receive update notifications | OP | | `autotreechop.replant` | Enable auto replanting | Everyone | | `autotreechop.leaves` | Enable leaves removal | Everyone | -| `autotreechop.autopickup` | Collect chopped drops straight into the inventory | Everyone | +| `autotreechop.autopickup` | Collect chopped drops straight into the inventory | OP | --- diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f13a3e4..5ac43e2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -89,7 +89,8 @@ increment-block-statistics: false # Put chopped blocks straight into the player's inventory instead of dropping them on the ground. # Respects the tool used and its enchantments (Fortune, Silk Touch), just like a normal block break. # Items that do not fit are dropped at the player's feet. -# Also requires the "autotreechop.autopickup" permission. +# Also requires the "autotreechop.autopickup" permission, which is not granted to everyone by +# default — give it to the ranks that should have the feature. # Players can turn it off for themselves with "/atc autopickup"; that choice is saved per player. enable-auto-pickup: false # Set the auto pickup state new players start with (only matters when enable-auto-pickup is true). diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1524479..ae75752 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,8 +14,9 @@ permissions: default: true autotreechop.leaves: default: true + # Paid/VIP feature: grant it explicitly, same tier as autotreechop.vip. autotreechop.autopickup: - default: true + default: op autotreechop.vip: default: op autotreechop.updatechecker: From aeed5b7937cf235c0c86888856e3d9902ebcb75c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 12:10:38 +0000 Subject: [PATCH 10/13] Route every message through the prefix style system again en and zh were rewritten with hardcoded colours in 0602c28, which took them out of the styles.properties system while de/ja/ms/ru stayed in it, and the later locales copied the hardcoded form. Editing prefix in styles.properties therefore did nothing for most of the server's players. Wrap every chat message in all ten locales in or so the prefix and the colour scheme are defined once in styles.properties. consoleName is a placeholder value rather than a message, and the four about lines are one block where a repeated prefix would be noise, so both keep raw MiniMessage. Default styles.properties values are unchanged, so the plugin still renders exactly as before until a server puts a prefix in front of {slot}; its comments now explain the two tags. Also drop the trailing full stop from every zh message, keeping the one that separates two sentences inside the three confirmation prompts. --- README.md | 17 +++++++ src/main/resources/lang/de.properties | 26 +++++------ src/main/resources/lang/en.properties | 54 +++++++++++------------ src/main/resources/lang/es.properties | 52 +++++++++++----------- src/main/resources/lang/fr.properties | 48 ++++++++++---------- src/main/resources/lang/it.properties | 48 ++++++++++---------- src/main/resources/lang/ja.properties | 6 +-- src/main/resources/lang/ms.properties | 6 +-- src/main/resources/lang/ru.properties | 6 +-- src/main/resources/lang/styles.properties | 19 +++++--- src/main/resources/lang/tr.properties | 48 ++++++++++---------- src/main/resources/lang/zh.properties | 54 +++++++++++------------ 12 files changed, 205 insertions(+), 179 deletions(-) diff --git a/README.md b/README.md index f128706..c031a42 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,23 @@ It's async-friendly, lightweight, and fully customizable — with built-in suppo --- +## Message styling + +Every chat message in `plugins/AutoTreeChop/lang/*.properties` is wrapped in one of two tags: +`` for success and information, `` for refusals, limits and warnings. +Both are defined in `lang/styles.properties`, so a server prefix or a different colour scheme is +a one-file change instead of an edit to every translation: + +```properties +prefix=[MyServer] {slot} +prefix_negative=[MyServer] {slot} +``` + +`{slot}` is the message text, and the values are [MiniMessage](https://docs.advntr.dev/minimessage/format.html). +Run `/atc reload` to apply changes. + +--- + ## Support & Contribute - Need help? Join our [Matrix](https://matrix.to/#/#maoyue-dev:matrix.org) diff --git a/src/main/resources/lang/de.properties b/src/main/resources/lang/de.properties index 46c3fe0..8518a53 100644 --- a/src/main/resources/lang/de.properties +++ b/src/main/resources/lang/de.properties @@ -10,21 +10,21 @@ hitmaxblock=Du hast dein tägliches Blocklimit zum Auto-Baumfä hitmaxusage=Du hast dein Tageslimit zum Auto-Baumfällen erreicht. no-permission=Dazu hast du keine Berechtigung usage=Du hast Auto-Baumfällen heute {current_uses}/{max_uses} mal verwendet. -noResidencePermissions=Du hast keine Berechtigung AutoTreeChop hier zu nutzen. -only-players=Dieser Befehl kann nur von Spielern benutzt werden. -stillInCooldown=AutoTreeChop kühlt sich ab. Bitte warte {cooldown_time} seconds. -confirmationRequiredIdle=AutoTreeChop wurde in der letzten Zeit nicht benutzt. Fälle ein Stamm erneut (oder/atc confirm) inerhalb {timeout}s to confirm. -confirmationRequiredNoLeaves=Keine Blätter in der Nähe erkannt. Dieser Baumstamm könnte von Spielern plaziert sein. Schlag wieder (oder /atc bestätige) innerhalb von {timeout}s um zu bestätigen. -confirmationRequiredBoth=AutoTreeChop wurde längere Zeit nicht verwendet und in der Nähe wurden keine Blätter erkannt. Schlag nochmal (oder /atc bestätige) innerhalb von {timeout}s um zu bestätigen. -confirmationSuccess=Erfolgreich bestätigt. AutoTreeChop ist nun aktiv. -noPendingConfirmation=Es gibt keine vorhandende AutoTreeChop-Bestätigung. -sneakEnabled=AutoTreeChop wurde beim Schleichen aktiviert. -sneakDisabled=AutoTreeChop wurde nach Ende des Schleichens deaktiviert. +noResidencePermissions=Du hast keine Berechtigung AutoTreeChop hier zu nutzen. +only-players=Dieser Befehl kann nur von Spielern benutzt werden. +stillInCooldown=AutoTreeChop kühlt sich ab. Bitte warte {cooldown_time} seconds. +confirmationRequiredIdle=AutoTreeChop wurde in der letzten Zeit nicht benutzt. Fälle ein Stamm erneut (oder/atc confirm) inerhalb {timeout}s to confirm. +confirmationRequiredNoLeaves=Keine Blätter in der Nähe erkannt. Dieser Baumstamm könnte von Spielern plaziert sein. Schlag wieder (oder /atc bestätige) innerhalb von {timeout}s um zu bestätigen. +confirmationRequiredBoth=AutoTreeChop wurde längere Zeit nicht verwendet und in der Nähe wurden keine Blätter erkannt. Schlag nochmal (oder /atc bestätige) innerhalb von {timeout}s um zu bestätigen. +confirmationSuccess=Erfolgreich bestätigt. AutoTreeChop ist nun aktiv. +noPendingConfirmation=Es gibt keine vorhandende AutoTreeChop-Bestätigung. +sneakEnabled=AutoTreeChop wurde beim Schleichen aktiviert. +sneakDisabled=AutoTreeChop wurde nach Ende des Schleichens deaktiviert. aboutHeader=AutoTreeChop - v{version} vom MilkTeaMC-Team und Contributors aboutLicense=Lizenz: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop -autoPickupEnabled=Automatisches Aufsammeln aktiviert. Gefällte Gegenstände landen direkt im Inventar. -autoPickupDisabled=Automatisches Aufsammeln deaktiviert. Gefällte Gegenstände fallen auf den Boden. -autoPickupUnavailable=Automatisches Aufsammeln ist auf diesem Server deaktiviert. +autoPickupEnabled=Automatisches Aufsammeln aktiviert. Gefällte Gegenstände landen direkt im Inventar. +autoPickupDisabled=Automatisches Aufsammeln deaktiviert. Gefällte Gegenstände fallen auf den Boden. +autoPickupUnavailable=Automatisches Aufsammeln ist auf diesem Server deaktiviert. diff --git a/src/main/resources/lang/en.properties b/src/main/resources/lang/en.properties index 6f9f52e..2520c3c 100644 --- a/src/main/resources/lang/en.properties +++ b/src/main/resources/lang/en.properties @@ -1,39 +1,39 @@ -noResidencePermissions=You do not have permission to use AutoTreeChop here. +noResidencePermissions=You do not have permission to use AutoTreeChop here. -enabled=AutoTreeChop enabled. -disabled=AutoTreeChop disabled. +enabled=AutoTreeChop enabled. +disabled=AutoTreeChop disabled. -enabledByOther=AutoTreeChop was enabled by {player}. -enabledForOther=AutoTreeChop was enabled for {player}. +enabledByOther=AutoTreeChop was enabled by {player}. +enabledForOther=AutoTreeChop was enabled for {player}. -disabledByOther=AutoTreeChop was disabled by {player}. -disabledForOther=AutoTreeChop was disabled for {player}. +disabledByOther=AutoTreeChop was disabled by {player}. +disabledForOther=AutoTreeChop was disabled for {player}. -no-permission=You do not have permission to perform this action. -only-players=This command can only be used by players. +no-permission=You do not have permission to perform this action. +only-players=This command can only be used by players. -hitmaxusage=You have reached your daily usage limit. -hitmaxblock=You have reached your daily block-breaking limit. +hitmaxusage=You have reached your daily usage limit. +hitmaxblock=You have reached your daily block-breaking limit. -usage=Daily AutoTreeChop uses: {current_uses}/{max_uses} -blocks-broken=Blocks broken today: {current_blocks}/{max_blocks} +usage=Daily AutoTreeChop uses: {current_uses}/{max_uses} +blocks-broken=Blocks broken today: {current_blocks}/{max_blocks} -stillInCooldown=AutoTreeChop is cooling down. Please wait {cooldown_time} seconds. +stillInCooldown=AutoTreeChop is cooling down. Please wait {cooldown_time} seconds. -confirmationRequiredIdle=AutoTreeChop has not been used recently. Chop a log again (or /atc confirm) within {timeout}s to confirm. -confirmationRequiredNoLeaves=No nearby leaves detected. This log may be player-placed. Chop again (or /atc confirm) within {timeout}s to confirm. -confirmationRequiredBoth=AutoTreeChop has not been used recently and no nearby leaves were detected. Chop again (or /atc confirm) within {timeout}s to confirm. +confirmationRequiredIdle=AutoTreeChop has not been used recently. Chop a log again (or /atc confirm) within {timeout}s to confirm. +confirmationRequiredNoLeaves=No nearby leaves detected. This log may be player-placed. Chop again (or /atc confirm) within {timeout}s to confirm. +confirmationRequiredBoth=AutoTreeChop has not been used recently and no nearby leaves were detected. Chop again (or /atc confirm) within {timeout}s to confirm. -confirmationSuccess=Confirmation successful. AutoTreeChop is now active. -noPendingConfirmation=There is no pending AutoTreeChop confirmation. +confirmationSuccess=Confirmation successful. AutoTreeChop is now active. +noPendingConfirmation=There is no pending AutoTreeChop confirmation. -sneakEnabled=AutoTreeChop enabled while sneaking. -sneakDisabled=AutoTreeChop disabled after stopping sneak. +sneakEnabled=AutoTreeChop enabled while sneaking. +sneakDisabled=AutoTreeChop disabled after stopping sneak. -alreadyEnabled=AutoTreeChop is already enabled. -alreadyDisabled=AutoTreeChop is already disabled. +alreadyEnabled=AutoTreeChop is already enabled. +alreadyDisabled=AutoTreeChop is already disabled. -inventoryFull=Your inventory is full. The remaining items were dropped at your feet. +inventoryFull=Your inventory is full. The remaining items were dropped at your feet. consoleName=console @@ -42,6 +42,6 @@ aboutLicense=License: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop -autoPickupEnabled=Auto pickup enabled. Chopped items now go straight into your inventory. -autoPickupDisabled=Auto pickup disabled. Chopped items now drop on the ground. -autoPickupUnavailable=Auto pickup is disabled on this server. +autoPickupEnabled=Auto pickup enabled. Chopped items now go straight into your inventory. +autoPickupDisabled=Auto pickup disabled. Chopped items now drop on the ground. +autoPickupUnavailable=Auto pickup is disabled on this server. diff --git a/src/main/resources/lang/es.properties b/src/main/resources/lang/es.properties index 5fd1185..fe2d27f 100644 --- a/src/main/resources/lang/es.properties +++ b/src/main/resources/lang/es.properties @@ -1,32 +1,32 @@ -blocks-broken=Bloques rotos hoy: {current_blocks}/{max_blocks} +blocks-broken=Bloques rotos hoy: {current_blocks}/{max_blocks} consoleName=Consola -disabled=AutoTreeChop desactivado. -disabledByOther=AutoTreeChop fue desactivado por {player}. -disabledForOther=AutoTreeChop fue desactivado para {player}. -enabled=AutoTreeChop habilitado. -enabledByOther=AutoTreeChop fue activado por {player}. -enabledForOther=AutoTreeChop se activó para {player}. -hitmaxblock=Has alcanzado tu límite diario de ruptura de bloques. -hitmaxusage=Has alcanzado tu límite de uso diario. -no-permission=No tienes permiso para realizar esta acción. -usage=Daily AutoTreeChop usó: {current_uses}/{max_uses} -noResidencePermissions=No tienes permiso para usar AutoTreeChop aquí. -stillInCooldown=AutoTreeChop se está enfriando. Por favor, espere {cooldown_time} segundos. -only-players=Este comando solo puede ser utilizado por jugadores. -confirmationRequiredIdle=AutoTreeChop no se ha utilizado recientemente. Recorte un registro de nuevo (o /atc confirm) dentro de {timeout}s para confirmar. -confirmationRequiredNoLeaves=No se detectó ninguna hoja cercana. Este registro puede estar colocado por el jugador. Cortar otra vez (o /atc confirm) dentro de {timeout}s para confirmar. -confirmationRequiredBoth=AutoTreeChop no se ha utilizado recientemente y no se han detectado hojas cercanas. Cortar otra vez (o /atc confirm) dentro de {timeout}s para confirmar. -confirmationSuccess=Confirmación exitosa. AutoTreeChop ahora está activo. -noPendingConfirmation=No hay ninguna confirmación pendiente de AutoTreeChop. -sneakEnabled=Se habilita la opción de seguimiento automático mientras se oculta. -sneakDisabled=La opción de hacer clic automáticamente se deshabilitó después de parar. +disabled=AutoTreeChop desactivado. +disabledByOther=AutoTreeChop fue desactivado por {player}. +disabledForOther=AutoTreeChop fue desactivado para {player}. +enabled=AutoTreeChop habilitado. +enabledByOther=AutoTreeChop fue activado por {player}. +enabledForOther=AutoTreeChop se activó para {player}. +hitmaxblock=Has alcanzado tu límite diario de ruptura de bloques. +hitmaxusage=Has alcanzado tu límite de uso diario. +no-permission=No tienes permiso para realizar esta acción. +usage=Daily AutoTreeChop usó: {current_uses}/{max_uses} +noResidencePermissions=No tienes permiso para usar AutoTreeChop aquí. +stillInCooldown=AutoTreeChop se está enfriando. Por favor, espere {cooldown_time} segundos. +only-players=Este comando solo puede ser utilizado por jugadores. +confirmationRequiredIdle=AutoTreeChop no se ha utilizado recientemente. Recorte un registro de nuevo (o /atc confirm) dentro de {timeout}s para confirmar. +confirmationRequiredNoLeaves=No se detectó ninguna hoja cercana. Este registro puede estar colocado por el jugador. Cortar otra vez (o /atc confirm) dentro de {timeout}s para confirmar. +confirmationRequiredBoth=AutoTreeChop no se ha utilizado recientemente y no se han detectado hojas cercanas. Cortar otra vez (o /atc confirm) dentro de {timeout}s para confirmar. +confirmationSuccess=Confirmación exitosa. AutoTreeChop ahora está activo. +noPendingConfirmation=No hay ninguna confirmación pendiente de AutoTreeChop. +sneakEnabled=Se habilita la opción de seguimiento automático mientras se oculta. +sneakDisabled=La opción de hacer clic automáticamente se deshabilitó después de parar. aboutHeader=AutoTreeChop - v{versión} por el equipo de MilkTeaMC y los colaboradores aboutLicense=Licencia: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop -alreadyEnabled=AutoTreeChop está activado. -alreadyDisabled=AutoTreeChop está desactivado. +alreadyEnabled=AutoTreeChop está activado. +alreadyDisabled=AutoTreeChop está desactivado. -autoPickupEnabled=Recogida automática activada. Los objetos talados van directo a tu inventario. -autoPickupDisabled=Recogida automática desactivada. Los objetos talados caerán al suelo. -autoPickupUnavailable=La recogida automática está desactivada en este servidor. +autoPickupEnabled=Recogida automática activada. Los objetos talados van directo a tu inventario. +autoPickupDisabled=Recogida automática desactivada. Los objetos talados caerán al suelo. +autoPickupUnavailable=La recogida automática está desactivada en este servidor. diff --git a/src/main/resources/lang/fr.properties b/src/main/resources/lang/fr.properties index 2876771..4cbf475 100644 --- a/src/main/resources/lang/fr.properties +++ b/src/main/resources/lang/fr.properties @@ -1,30 +1,30 @@ -blocks-broken=Tu as cassé {current_blocks}/{max_blocks} blocs aujourd'hui. +blocks-broken=Tu as cassé {current_blocks}/{max_blocks} blocs aujourd'hui. consoleName=console -disabled=AutoTreeChop désactivé. -disabledByOther=AutoTreeChop désactivé par {player}. -disabledForOther=AutoTreeChop désactivé pour {player} -enabled=AutoTreeChop activé. -enabledByOther=AutoTreeChop activé par {player} -enabledForOther=AutoTreeChop activé pour {player} -hitmaxblock=Tu as atteint ta limite quotidienne de destruction de blocs. -hitmaxusage=Tu as atteint ta limite d'utilisation quotidienne. -no-permission=Tu n'as pas la permission de faire ça. -usage=Tu as utilisé AutoTreeChop {current_uses}/{max_uses} fois aujourd'hui. -noResidencePermissions=Tu n'as pas la permission d'utiliser AutoTreeChop ici. -stillInCooldown=Tu es encore en temps de recharge ! Réessaie après {cooldown_time} secondes. -only-players=Cette commande peut seulement être utilisée par les joueurs. -confirmationRequiredIdle=AutoTreeChop n'a pas été récemment utilisé. Coupe une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. -confirmationSuccess=Confirmation réussie. AutoTreeChop est maintenant actif. -noPendingConfirmation=Il n'y a pas de confirmation d'AutoTreeChop en attente. -sneakEnabled=AutoTreeChop activé en étant accroupi. -sneakDisabled=AutoTreeChop désactivé en n'étant plus accroupi . +disabled=AutoTreeChop désactivé. +disabledByOther=AutoTreeChop désactivé par {player}. +disabledForOther=AutoTreeChop désactivé pour {player} +enabled=AutoTreeChop activé. +enabledByOther=AutoTreeChop activé par {player} +enabledForOther=AutoTreeChop activé pour {player} +hitmaxblock=Tu as atteint ta limite quotidienne de destruction de blocs. +hitmaxusage=Tu as atteint ta limite d'utilisation quotidienne. +no-permission=Tu n'as pas la permission de faire ça. +usage=Tu as utilisé AutoTreeChop {current_uses}/{max_uses} fois aujourd'hui. +noResidencePermissions=Tu n'as pas la permission d'utiliser AutoTreeChop ici. +stillInCooldown=Tu es encore en temps de recharge ! Réessaie après {cooldown_time} secondes. +only-players=Cette commande peut seulement être utilisée par les joueurs. +confirmationRequiredIdle=AutoTreeChop n'a pas été récemment utilisé. Coupe une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. +confirmationSuccess=Confirmation réussie. AutoTreeChop est maintenant actif. +noPendingConfirmation=Il n'y a pas de confirmation d'AutoTreeChop en attente. +sneakEnabled=AutoTreeChop activé en étant accroupi. +sneakDisabled=AutoTreeChop désactivé en n'étant plus accroupi . aboutHeader=AutoTreeChop - v{version} par l'équipe MilkTeaMC et les contributeurs aboutLicense=Licence: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop -confirmationRequiredNoLeaves=Pas de feuillage détecté à proximité. Cette bûche a pu être placée par un joueur. Couper une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. -confirmationRequiredBoth=AutoTreeChop n'a pas été récemment utilisé et aucun feuillage n'a été détecté à proximité. Coupez une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. +confirmationRequiredNoLeaves=Pas de feuillage détecté à proximité. Cette bûche a pu être placée par un joueur. Couper une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. +confirmationRequiredBoth=AutoTreeChop n'a pas été récemment utilisé et aucun feuillage n'a été détecté à proximité. Coupez une nouvelle bûche (ou /atc confirm) avant {timeout}s pour confirmer. -autoPickupEnabled=Ramassage automatique activé. Les objets coupés vont directement dans votre inventaire. -autoPickupDisabled=Ramassage automatique désactivé. Les objets coupés tombent au sol. -autoPickupUnavailable=Le ramassage automatique est désactivé sur ce serveur. +autoPickupEnabled=Ramassage automatique activé. Les objets coupés vont directement dans votre inventaire. +autoPickupDisabled=Ramassage automatique désactivé. Les objets coupés tombent au sol. +autoPickupUnavailable=Le ramassage automatique est désactivé sur ce serveur. diff --git a/src/main/resources/lang/it.properties b/src/main/resources/lang/it.properties index b4844c7..49b10e2 100644 --- a/src/main/resources/lang/it.properties +++ b/src/main/resources/lang/it.properties @@ -1,30 +1,30 @@ -noResidencePermissions=Non hai i permessi per usare AutoTreeChop qui. -enabled=AutoTreeChop attivato. -disabled=AutoTreeChop disattivato. -enabledByOther=AutoTreeChop è stato attivato da {player}. -enabledForOther=AutoTreeChop è stato attivato per {player}. -disabledByOther=AutoTreeChop è stato disattivato da {player}. -disabledForOther=AutoTreeChop è stato disattivato per {player}. -no-permission=Non hai i permessi per effettuare questa azione. -only-players=Questo comando può essere usato soltanto dai giocatori. -hitmaxusage=Hai raggiunto il tuo limite di utilizzo giornaliero. -hitmaxblock=Hai raggiunto il tuo limite giornaliero di blocchi distrutti. -usage=Utilizzi giornalieri di AutoTreeChop: {current_uses}/{max_uses} -blocks-broken=Blocchi distrutti oggi: {current_blocks}/{max_blocks} -stillInCooldown=AutoTreeChop è in cooldown. Per favore attendi {cooldown_time} secondi. -confirmationRequiredIdle=AutoTreeChop non è stato utilizzato di recente. Distruggi un tronco (o digita /atc confirm) entro {timeout} s per confermare. -confirmationRequiredNoLeaves=Non ci sono foglie rilevate nelle vicinanze. Questo tronco potrebbe essere stato piazzato da un giocatore. Taglia ancora (o digita /atc confirm) entro {timeout} s per confermare. -confirmationRequiredBoth=AutoTreeChop non è stato utilizzato di recente e non ci sono foglie rilevate nelle vicinanze. Taglia ancora (o digita /atc confirm) entro {timeout} s per confermare. -confirmationSuccess=Conferma effettuata. AutoTreeChop è ora attivo. -noPendingConfirmation=Non ci sono richieste di conferma di AutoTreeChop in sospeso. -sneakEnabled=AutoTreeChop è stato attivato durante l'accovacciamento. -sneakDisabled=AutoTreeChop disattivato dopo aver interrotto l'accovacciamento. +noResidencePermissions=Non hai i permessi per usare AutoTreeChop qui. +enabled=AutoTreeChop attivato. +disabled=AutoTreeChop disattivato. +enabledByOther=AutoTreeChop è stato attivato da {player}. +enabledForOther=AutoTreeChop è stato attivato per {player}. +disabledByOther=AutoTreeChop è stato disattivato da {player}. +disabledForOther=AutoTreeChop è stato disattivato per {player}. +no-permission=Non hai i permessi per effettuare questa azione. +only-players=Questo comando può essere usato soltanto dai giocatori. +hitmaxusage=Hai raggiunto il tuo limite di utilizzo giornaliero. +hitmaxblock=Hai raggiunto il tuo limite giornaliero di blocchi distrutti. +usage=Utilizzi giornalieri di AutoTreeChop: {current_uses}/{max_uses} +blocks-broken=Blocchi distrutti oggi: {current_blocks}/{max_blocks} +stillInCooldown=AutoTreeChop è in cooldown. Per favore attendi {cooldown_time} secondi. +confirmationRequiredIdle=AutoTreeChop non è stato utilizzato di recente. Distruggi un tronco (o digita /atc confirm) entro {timeout} s per confermare. +confirmationRequiredNoLeaves=Non ci sono foglie rilevate nelle vicinanze. Questo tronco potrebbe essere stato piazzato da un giocatore. Taglia ancora (o digita /atc confirm) entro {timeout} s per confermare. +confirmationRequiredBoth=AutoTreeChop non è stato utilizzato di recente e non ci sono foglie rilevate nelle vicinanze. Taglia ancora (o digita /atc confirm) entro {timeout} s per confermare. +confirmationSuccess=Conferma effettuata. AutoTreeChop è ora attivo. +noPendingConfirmation=Non ci sono richieste di conferma di AutoTreeChop in sospeso. +sneakEnabled=AutoTreeChop è stato attivato durante l'accovacciamento. +sneakDisabled=AutoTreeChop disattivato dopo aver interrotto l'accovacciamento. consoleName=console aboutHeader=AutoTreeChop - v{version} di MilkTeaMC team e collaboratori aboutLicense=Licenza: GNU General Public License v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth: https://modrinth.com/plugin/autotreechop -autoPickupEnabled=Raccolta automatica attivata. Gli oggetti tagliati vanno direttamente nell'inventario. -autoPickupDisabled=Raccolta automatica disattivata. Gli oggetti tagliati cadranno a terra. -autoPickupUnavailable=La raccolta automatica è disattivata su questo server. +autoPickupEnabled=Raccolta automatica attivata. Gli oggetti tagliati vanno direttamente nell'inventario. +autoPickupDisabled=Raccolta automatica disattivata. Gli oggetti tagliati cadranno a terra. +autoPickupUnavailable=La raccolta automatica è disattivata su questo server. diff --git a/src/main/resources/lang/ja.properties b/src/main/resources/lang/ja.properties index 464961a..3ca6223 100644 --- a/src/main/resources/lang/ja.properties +++ b/src/main/resources/lang/ja.properties @@ -13,6 +13,6 @@ usage=今日は自動伐採を{current_uses}/{max_uses}回使用しま noResidencePermissions=ここでは自動伐採を使用する権限がありません。 stillInCooldown=まだクールダウン中です!{cooldown_time}秒後にもう一度お試しください。 -autoPickupEnabled=自動回収を有効にしました。伐採したアイテムは直接インベントリに入ります。 -autoPickupDisabled=自動回収を無効にしました。伐採したアイテムは地面にドロップします。 -autoPickupUnavailable=このサーバーでは自動回収が無効になっています。 +autoPickupEnabled=自動回収を有効にしました。伐採したアイテムは直接インベントリに入ります。 +autoPickupDisabled=自動回収を無効にしました。伐採したアイテムは地面にドロップします。 +autoPickupUnavailable=このサーバーでは自動回収が無効になっています。 diff --git a/src/main/resources/lang/ms.properties b/src/main/resources/lang/ms.properties index 596ed4e..d246ef6 100644 --- a/src/main/resources/lang/ms.properties +++ b/src/main/resources/lang/ms.properties @@ -16,6 +16,6 @@ consoleName=konsol sneakEnabled=Fungsi Penebangan pokok automatik diaktifkan semasa mencangkung. sneakDisabled=Fungsi Penebangan pokok automatik ditutup semasa mencangkung. -autoPickupEnabled=Kutipan automatik dihidupkan. Item yang ditebang terus masuk ke inventori anda. -autoPickupDisabled=Kutipan automatik dimatikan. Item yang ditebang akan jatuh ke tanah. -autoPickupUnavailable=Kutipan automatik dilumpuhkan di pelayan ini. +autoPickupEnabled=Kutipan automatik dihidupkan. Item yang ditebang terus masuk ke inventori anda. +autoPickupDisabled=Kutipan automatik dimatikan. Item yang ditebang akan jatuh ke tanah. +autoPickupUnavailable=Kutipan automatik dilumpuhkan di pelayan ini. diff --git a/src/main/resources/lang/ru.properties b/src/main/resources/lang/ru.properties index 1a01e90..2a04178 100644 --- a/src/main/resources/lang/ru.properties +++ b/src/main/resources/lang/ru.properties @@ -16,6 +16,6 @@ only-players=Эта команда может быть испо sneakEnabled=Автоматическая рубка деревьев включается тайком. sneakDisabled=Автоматическая рубка деревьев отключена путем остановки подкрадывания. -autoPickupEnabled=Автоподбор включён. Срубленные предметы попадают прямо в инвентарь. -autoPickupDisabled=Автоподбор выключен. Срубленные предметы будут падать на землю. -autoPickupUnavailable=Автоподбор отключён на этом сервере. +autoPickupEnabled=Автоподбор включён. Срубленные предметы попадают прямо в инвентарь. +autoPickupDisabled=Автоподбор выключен. Срубленные предметы будут падать на землю. +autoPickupUnavailable=Автоподбор отключён на этом сервере. diff --git a/src/main/resources/lang/styles.properties b/src/main/resources/lang/styles.properties index e318c70..3b437d5 100644 --- a/src/main/resources/lang/styles.properties +++ b/src/main/resources/lang/styles.properties @@ -1,8 +1,17 @@ -# To apply the original style of the plugin we don't show a prefix at all and simply render in text color +# Every chat message in the lang files is wrapped in exactly one of two tags: +# ... success and information +# ... refusals, limits and warnings +# so the server prefix and the colour scheme are changed here, in one place, +# instead of in every translation. {slot} is the message text. +# +# The shipped defaults show no prefix at all and only colour the text, which is +# how the plugin has always looked. Put your server's prefix in front of {slot} +# in both tags to brand the messages, for example: +# prefix=[MyServer] {slot} prefix={slot} -# We therefore make text green -text={slot} prefix_negative={slot} -# and just to be very safe that it always looks like the original plugin + +# Tone of the message body, shared by the two tags above. +text={slot} positive={slot} -negative={slot} \ No newline at end of file +negative={slot} diff --git a/src/main/resources/lang/tr.properties b/src/main/resources/lang/tr.properties index 79e12c3..69e9bbe 100644 --- a/src/main/resources/lang/tr.properties +++ b/src/main/resources/lang/tr.properties @@ -1,30 +1,30 @@ -noResidencePermissions=Burada AutoTreeChopper'ı kullanmak için izniniz yok. -enabled=AutoTreeChop aktif. -disabled=AutoTreeChop devre dışı. -enabledByOther=AutoTreeChop {player} tarafından etkinleştirildi. -enabledForOther=AutoTreeChop {player} için etkinleştirildi. -disabledByOther=AutoTreeChop {player} tarafından devre dışı bırakıldı. -disabledForOther=AutoTreeChop {player} için devre dışı bırakıldı. -no-permission=Bu eylemi gerçekleştirmek için izniniz yok. -only-players=Bu komut sadece oyuncular tarafından kullanılabilir. -hitmaxusage=Günlük kullanım limitinize ulaştınız. -hitmaxblock=Günlük blok kırma limitinize ulaştınız. -usage=Günlük AutoTreeChop kullanımı: {current_uses}/{max_uses} -blocks-broken=Bugün kırılan bloklar: {current_blocks}/{max_blocks} -stillInCooldown=AutoTreeChop dinleniyor. Lütfen {cooldown_time} saniye bekleyiniz. -confirmationRequiredIdle=AutoTreeChop son zamanlarda kullanılmadı. Onaylamak için {timeout}s içinde tekrar bir kütük kesin (veya /atc confirm) komutunu çalıştırın. -confirmationRequiredNoLeaves=Yakınlarda yaprak tespit edilmedi. Bu kütük oyuncu tarafından yerleştirilmiş olabilir. Onaylamak için {timeout}s içinde tekrar kesin (veya /atc confirm) komutunu kullanın. -confirmationRequiredBoth=AutoTreeChop yakın zamanda kullanılmadı ve yakınlarda yaprak tespit edilmedi. Onaylamak için {timeout}s içinde tekrar kesin (veya /atc confirm komutunu çalıştırın). -confirmationSuccess=Onaylandı. AutoTreeChop şimdi aktif. -noPendingConfirmation=Beklenen bir AutoTreeChop onayı yok. -sneakEnabled=AutoTreeChop eğilirken aktif olacak. -sneakDisabled=AutoTreeChop eğildikten sonra devre dışı. +noResidencePermissions=Burada AutoTreeChopper'ı kullanmak için izniniz yok. +enabled=AutoTreeChop aktif. +disabled=AutoTreeChop devre dışı. +enabledByOther=AutoTreeChop {player} tarafından etkinleştirildi. +enabledForOther=AutoTreeChop {player} için etkinleştirildi. +disabledByOther=AutoTreeChop {player} tarafından devre dışı bırakıldı. +disabledForOther=AutoTreeChop {player} için devre dışı bırakıldı. +no-permission=Bu eylemi gerçekleştirmek için izniniz yok. +only-players=Bu komut sadece oyuncular tarafından kullanılabilir. +hitmaxusage=Günlük kullanım limitinize ulaştınız. +hitmaxblock=Günlük blok kırma limitinize ulaştınız. +usage=Günlük AutoTreeChop kullanımı: {current_uses}/{max_uses} +blocks-broken=Bugün kırılan bloklar: {current_blocks}/{max_blocks} +stillInCooldown=AutoTreeChop dinleniyor. Lütfen {cooldown_time} saniye bekleyiniz. +confirmationRequiredIdle=AutoTreeChop son zamanlarda kullanılmadı. Onaylamak için {timeout}s içinde tekrar bir kütük kesin (veya /atc confirm) komutunu çalıştırın. +confirmationRequiredNoLeaves=Yakınlarda yaprak tespit edilmedi. Bu kütük oyuncu tarafından yerleştirilmiş olabilir. Onaylamak için {timeout}s içinde tekrar kesin (veya /atc confirm) komutunu kullanın. +confirmationRequiredBoth=AutoTreeChop yakın zamanda kullanılmadı ve yakınlarda yaprak tespit edilmedi. Onaylamak için {timeout}s içinde tekrar kesin (veya /atc confirm komutunu çalıştırın). +confirmationSuccess=Onaylandı. AutoTreeChop şimdi aktif. +noPendingConfirmation=Beklenen bir AutoTreeChop onayı yok. +sneakEnabled=AutoTreeChop eğilirken aktif olacak. +sneakDisabled=AutoTreeChop eğildikten sonra devre dışı. consoleName=konsol aboutHeader=AutoTreeChop - v{version} MilkTeaMC ekibi ve katkıda bulunanlar tarafından aboutLicense=Lisans: GNU Genel Kamu Lisansı v3.0 (GPL-3.0) aboutGithub=GitHub: https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth Sitesi: https://modrinth.com/plugin/autotreechop -autoPickupEnabled=Otomatik toplama açıldı. Kesilen eşyalar doğrudan envanterine gider. -autoPickupDisabled=Otomatik toplama kapatıldı. Kesilen eşyalar yere düşer. -autoPickupUnavailable=Bu sunucuda otomatik toplama devre dışı. +autoPickupEnabled=Otomatik toplama açıldı. Kesilen eşyalar doğrudan envanterine gider. +autoPickupDisabled=Otomatik toplama kapatıldı. Kesilen eşyalar yere düşer. +autoPickupUnavailable=Bu sunucuda otomatik toplama devre dışı. diff --git a/src/main/resources/lang/zh.properties b/src/main/resources/lang/zh.properties index 75eede3..22fabe2 100644 --- a/src/main/resources/lang/zh.properties +++ b/src/main/resources/lang/zh.properties @@ -1,39 +1,39 @@ -noResidencePermissions=你沒有在此使用 AutoTreeChop 的權限。 +noResidencePermissions=你沒有在此使用 AutoTreeChop 的權限 -enabled=自動砍樹已啟用。 -disabled=自動砍樹已停用。 +enabled=自動砍樹已啟用 +disabled=自動砍樹已停用 -enabledByOther=自動砍樹已由 {player} 啟用。 -enabledForOther=已為 {player} 啟用自動砍樹。 +enabledByOther=自動砍樹已由 {player} 啟用 +enabledForOther=已為 {player} 啟用自動砍樹 -disabledByOther=自動砍樹已由 {player} 停用。 -disabledForOther=已為 {player} 停用自動砍樹。 +disabledByOther=自動砍樹已由 {player} 停用 +disabledForOther=已為 {player} 停用自動砍樹 -no-permission=你沒有執行此操作的權限。 -only-players=此指令僅限玩家使用。 +no-permission=你沒有執行此操作的權限 +only-players=此指令僅限玩家使用 -hitmaxusage=已達到本日可使用次數上限。 -hitmaxblock=已達到本日可破壞方塊數量上限。 +hitmaxusage=已達到本日可使用次數上限 +hitmaxblock=已達到本日可破壞方塊數量上限 -usage=今日自動砍樹使用次數:{current_uses}/{max_uses} -blocks-broken=今日已破壞方塊:{current_blocks}/{max_blocks} +usage=今日自動砍樹使用次數:{current_uses}/{max_uses} +blocks-broken=今日已破壞方塊:{current_blocks}/{max_blocks} -stillInCooldown=自動砍樹仍在冷卻中,請等待 {cooldown_time} 秒。 +stillInCooldown=自動砍樹仍在冷卻中,請等待 {cooldown_time} 秒 -confirmationRequiredIdle=自動砍樹已一段時間未使用。請再次砍伐原木,或在 {timeout}s 內輸入 /atc confirm 以確認。 -confirmationRequiredNoLeaves=附近未偵測到樹葉,此方塊可能為放置方塊。請再次砍伐,或在 {timeout}s 內輸入 /atc confirm 以確認。 -confirmationRequiredBoth=自動砍樹已久未使用,且附近未偵測到樹葉。請再次砍伐,或在 {timeout}s 內輸入 /atc confirm 以確認。 +confirmationRequiredIdle=自動砍樹已一段時間未使用。請再次砍伐原木,或在 {timeout}s 內輸入 /atc confirm 以確認 +confirmationRequiredNoLeaves=附近未偵測到樹葉,此方塊可能為放置方塊。請再次砍伐,或在 {timeout}s 內輸入 /atc confirm 以確認 +confirmationRequiredBoth=自動砍樹已久未使用,且附近未偵測到樹葉。請再次砍伐,或在 {timeout}s 內輸入 /atc confirm 以確認 -confirmationSuccess=確認完成,自動砍樹已啟用。 -noPendingConfirmation=目前沒有待確認的自動砍樹操作。 +confirmationSuccess=確認完成,自動砍樹已啟用 +noPendingConfirmation=目前沒有待確認的自動砍樹操作 -sneakEnabled=因進入潛行狀態,自動砍樹已啟用。 -sneakDisabled=已離開潛行狀態,自動砍樹已停用。 +sneakEnabled=因進入潛行狀態,自動砍樹已啟用 +sneakDisabled=已離開潛行狀態,自動砍樹已停用 -alreadyEnabled=自動砍樹已經是啟用狀態。 -alreadyDisabled=自動砍樹已經是停用狀態。 +alreadyEnabled=自動砍樹已經是啟用狀態 +alreadyDisabled=自動砍樹已經是停用狀態 -inventoryFull=你的背包已滿,剩餘的物品掉落在你腳邊。 +inventoryFull=你的背包已滿,剩餘的物品掉落在你腳邊 consoleName=控制台 @@ -42,6 +42,6 @@ aboutLicense=授權條款:GNU General Public License v3.0 (GPL-3.0)GitHub:https://github.com/milkteamc/autotreechop aboutModrinth=Modrinth:https://modrinth.com/plugin/autotreechop -autoPickupEnabled=自動收集已啟用,砍下的物品會直接進入背包。 -autoPickupDisabled=自動收集已停用,砍下的物品會掉落在地上。 -autoPickupUnavailable=本伺服器已停用自動收集功能。 +autoPickupEnabled=自動收集已啟用,砍下的物品會直接進入背包 +autoPickupDisabled=自動收集已停用,砍下的物品會掉落在地上 +autoPickupUnavailable=本伺服器已停用自動收集功能 From 9ba61ad140ec1770602fef971a6bc6d1468b4438 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 13:55:30 +0000 Subject: [PATCH 11/13] Honour the player's tool when drops fall on the ground removeLeafBlock() and the log batch both take the player's tool, but only the auto pickup branch used it: the other branch called breakNaturally() with no argument, which rolls drops as if the block had been broken with nothing in hand. The result was that auto pickup, a setting about where drops end up, silently decided what they are. With leaf-removal-drop-items enabled, a player with auto pickup on got leaf blocks from a Silk Touch axe and extra saplings from Fortune, while a player with it off got neither from the exact same axe. Route both branches through DropCollectionUtils.breakNaturally(), which mirrors the null/AIR tool handling collectDrops() already does, so the two paths cannot drift apart again. The log branch has no visible change today (logs drop themselves regardless of enchantments) but had the same defect. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Xsw1ZoP2KD2x9oEBVXJuwu --- .../autotreechop/utils/DropCollectionUtils.java | 16 ++++++++++++++++ .../autotreechop/utils/TreeChopUtils.java | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java index aa703ac..04c3cbb 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java @@ -87,6 +87,22 @@ public static void collectDrops(Block block, ItemStack tool, Player player, List } } + /** + * Breaks a block and lets its drops fall on the ground, honouring the tool the player is + * actually holding. + * + *

Counterpart to {@link #collectDrops} for players without auto pickup. Both must treat the + * tool the same way, otherwise the same enchanted axe yields different items depending on a + * setting that is only supposed to decide where the drops end up, not what they are. + */ + public static void breakNaturally(Block block, ItemStack tool) { + if (tool == null || XMaterial.matchXMaterial(tool) == XMaterial.AIR) { + block.breakNaturally(); + } else { + block.breakNaturally(tool); + } + } + /** * Puts everything collected into the player's inventory. Whatever does not fit is dropped at * the player's feet and the player is told once (rate limited, because logs and leaves are diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index d5ed76f..5f5966c 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -382,7 +382,7 @@ private void executeTreeChop( } block.setType(XMaterial.AIR.get(), false); } else { - block.breakNaturally(); + DropCollectionUtils.breakNaturally(block, tool); } if (config.isIncrementBlockStatistics()) { @@ -676,7 +676,7 @@ private boolean removeLeafBlock( DropCollectionUtils.collectDrops(leafBlock, tool, player, collectedDrops); leafBlock.setType(XMaterial.AIR.get(), false); } else { - leafBlock.breakNaturally(); + DropCollectionUtils.breakNaturally(leafBlock, tool); } if (config.isIncrementBlockStatistics()) { From 5f9aa89cc8dea5ae56e5e9777fb02080e036e106 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 14:04:30 +0000 Subject: [PATCH 12/13] Drop leaf blocks for Silk Touch tools independently of leaf-removal-drop-items leaf-removal-drop-items is an all-or-nothing switch: leaving it off (the default) means leaves vanish even for a Silk Touch axe, and turning it on showers every player with saplings, sticks and apples. Servers that want the vanilla Silk Touch behaviour had to accept the litter as well. Add leaf-removal-silk-touch-drops (default true), checked alongside the existing flag when deciding whether a leaf yields anything. Once either says yes, the drops themselves are already correct: both the auto pickup and the ground-drop path read the tool, so Silk Touch produces the leaf block and an unenchanted axe the usual sapling roll. The enchantment is resolved once per chop next to the auto pickup lookup, not per leaf, since the tool cannot change while a removal is running. Shears are deliberately not recognised: chain chopping starts from a log break, so shears never reach this path in practice. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Xsw1ZoP2KD2x9oEBVXJuwu --- .../java/org/milkteamc/autotreechop/Config.java | 6 ++++++ .../autotreechop/utils/TreeChopUtils.java | 14 +++++++++++++- src/main/resources/config.yml | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/milkteamc/autotreechop/Config.java b/src/main/java/org/milkteamc/autotreechop/Config.java index 94bac52..7f7297f 100644 --- a/src/main/java/org/milkteamc/autotreechop/Config.java +++ b/src/main/java/org/milkteamc/autotreechop/Config.java @@ -89,6 +89,7 @@ public class Config { private long leafRemovalDelayTicks; private int leafRemovalRadius; private boolean leafRemovalDropItems; + private boolean leafRemovalSilkTouchDrops; private boolean leafRemovalVisualEffects; private boolean leafRemovalAsync; private int leafRemovalBatchSize; @@ -243,6 +244,7 @@ private void loadValues() { leafRemovalDelayTicks = config.getLong("leaf-removal-delay-ticks", 5L); leafRemovalRadius = config.getInt("leaf-removal-radius", 10); leafRemovalDropItems = config.getBoolean("leaf-removal-drop-items", false); + leafRemovalSilkTouchDrops = config.getBoolean("leaf-removal-silk-touch-drops", true); leafRemovalVisualEffects = config.getBoolean("leaf-removal-visual-effects", true); leafRemovalAsync = config.getBoolean("leaf-removal-async", true); leafRemovalBatchSize = config.getInt("leaf-removal-batch-size", 20); @@ -503,6 +505,10 @@ public boolean getLeafRemovalDropItems() { return leafRemovalDropItems; } + public boolean getLeafRemovalSilkTouchDrops() { + return leafRemovalSilkTouchDrops; + } + public boolean getLeafRemovalVisualEffects() { return leafRemovalVisualEffects; } diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 5f5966c..6e2fcca 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -120,6 +120,13 @@ private static int getUnbreakingLevel(ItemStack item) { return 0; } + private static boolean hasSilkTouch(ItemStack item) { + if (item != null && item.hasItemMeta() && item.getItemMeta().hasEnchants()) { + return item.getEnchantmentLevel(XEnchantment.SILK_TOUCH.get()) > 0; + } + return false; + } + private static boolean shouldApplyDurabilityLoss(int unbreakingLevel, Config config) { if (unbreakingLevel <= 0 || !config.getRespectUnbreaking()) { return true; @@ -574,6 +581,9 @@ private void executeLeafRemoval( int batchSize = config.getLeafRemovalBatchSize(); Map leafStatCounts = new HashMap<>(); boolean autoPickup = DropCollectionUtils.isAutoPickupEnabledForPlayer(player, playerConfig, config); + // Resolved once per chop rather than per leaf: the tool cannot change mid-removal. + boolean dropItems = + config.getLeafRemovalDropItems() || (config.getLeafRemovalSilkTouchDrops() && hasSilkTouch(tool)); List collectedDrops = new ArrayList<>(); batchProcessor.processBatchWithTermination( @@ -601,6 +611,7 @@ private void executeLeafRemoval( hooks, leafStatCounts, autoPickup, + dropItems, collectedDrops); return true; // Continue processing @@ -633,6 +644,7 @@ private boolean removeLeafBlock( ProtectionCheckUtils.ProtectionHooks hooks, Map leafStatCounts, boolean autoPickup, + boolean dropItems, List collectedDrops) { Location leafLocation = leafBlock.getLocation(); @@ -669,7 +681,7 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } - if (!config.getLeafRemovalDropItems()) { + if (!dropItems) { leafBlock.setType(XMaterial.AIR.get(), false); } else if (autoPickup) { // Drops must be read before the block is cleared, otherwise it is already air. diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5ac43e2..2a2a911 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -133,6 +133,10 @@ leaf-removal-delay-ticks: 5 leaf-removal-radius: 10 # Whether removed leaves should drop items leaf-removal-drop-items: false +# Whether leaves broken with a Silk Touch tool should drop the leaf block itself. +# Independent of leaf-removal-drop-items, so leaves can keep vanishing for everyone else +# while Silk Touch still behaves the way it does in vanilla. +leaf-removal-silk-touch-drops: true # Show particle effects when removing leaves leaf-removal-visual-effects: true # Process leaf removal asynchronously From 0c74b0930d2563efaa393fb44d38f8f00ffd4453 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 14:12:21 +0000 Subject: [PATCH 13/13] Record the leaf mining statistic before the leaf is broken removeLeafBlock() read leafBlock.getType() after removing the block, so every leaf resolved to AIR: with increment-block-statistics enabled the player's stats file accumulated minecraft:mined -> minecraft:air, the real per-leaf statistics stayed at zero, and because Material was the map key all leaf types collapsed into that one bogus entry. Capture the type before the break, matching how originalLogType is already handled on the log side. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Xsw1ZoP2KD2x9oEBVXJuwu --- .../java/org/milkteamc/autotreechop/utils/TreeChopUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java index 6e2fcca..9be9b53 100644 --- a/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java +++ b/src/main/java/org/milkteamc/autotreechop/utils/TreeChopUtils.java @@ -681,6 +681,10 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } + // Read before the break, like originalLogType on the log side: afterwards the block is + // air (or water, for a waterlogged leaf) and every leaf type collapses into one entry. + Material leafMaterial = leafBlock.getType(); + if (!dropItems) { leafBlock.setType(XMaterial.AIR.get(), false); } else if (autoPickup) { @@ -692,7 +696,6 @@ private boolean removeLeafBlock( } if (config.isIncrementBlockStatistics()) { - Material leafMaterial = leafBlock.getType(); leafStatCounts.merge(leafMaterial, 1, Integer::sum); }