diff --git a/README.md b/README.md index 64a0763..c031a42 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 @@ -70,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 | @@ -85,13 +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 | OP | --- @@ -107,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/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 ded9b7e..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; @@ -104,6 +105,9 @@ public class Config { private int maxTreeSize; private int maxDiscoveryBlocks; private boolean callBlockBreakEvent; + private boolean incrementBlockStatistics; + private boolean autoPickupEnabled; + private boolean defaultAutoPickup; public Config(AutoTreeChop plugin) { this.plugin = plugin; @@ -227,6 +231,9 @@ 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); + 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); @@ -237,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); @@ -497,6 +505,10 @@ public boolean getLeafRemovalDropItems() { return leafRemovalDropItems; } + public boolean getLeafRemovalSilkTouchDrops() { + return leafRemovalSilkTouchDrops; + } + public boolean getLeafRemovalVisualEffects() { return leafRemovalVisualEffects; } @@ -537,6 +549,18 @@ public boolean isCallBlockBreakEvent() { return callBlockBreakEvent; } + public boolean isIncrementBlockStatistics() { + return incrementBlockStatistics; + } + + 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 3eb53e7..de1723c 100644 --- a/src/main/java/org/milkteamc/autotreechop/MessageKeys.java +++ b/src/main/java/org/milkteamc/autotreechop/MessageKeys.java @@ -43,8 +43,13 @@ 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"; 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/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..5ad1e8c 100644 --- a/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java +++ b/src/main/java/org/milkteamc/autotreechop/command/ToggleCommand.java @@ -182,6 +182,46 @@ 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. + * + *

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. 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; + } + + if (!player.hasPermission("autotreechop.autopickup")) { + AutoTreeChop.sendMessage(player, MessageKeys.NO_PERMISSION); + 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/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..04c3cbb --- /dev/null +++ b/src/main/java/org/milkteamc/autotreechop/utils/DropCollectionUtils.java @@ -0,0 +1,191 @@ +/* + * 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; +import org.milkteamc.autotreechop.PlayerConfig; + +/** + * 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 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, PlayerConfig playerConfig, Config config) { + return config.isAutoPickupEnabled() + && player.hasPermission("autotreechop.autopickup") + && playerConfig.isAutoPickupEnabled(); + } + + /** + * 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()); + } + } + + /** + * 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 + * 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 9cbde37..9be9b53 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; @@ -38,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; @@ -112,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; @@ -292,17 +307,41 @@ 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, playerConfig, config); + List collectedDrops = new ArrayList<>(); batchProcessor.processBatch( blockList, @@ -323,9 +362,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()); } @@ -340,7 +379,22 @@ 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 { + DropCollectionUtils.breakNaturally(block, tool); + } + + if (config.isIncrementBlockStatistics()) { + logStatCounts.merge(originalLogType, 1, Integer::sum); + } actuallyRemovedLogs.add(location); sessionManager.trackRemovedLogForPlayer(playerUUID.toString(), location); @@ -348,27 +402,36 @@ 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); } + if (autoPickup) { + DropCollectionUtils.deliverDrops(player, collectedDrops); + } + // 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, playerConfig, hooks, actuallyRemovedLogs); }; - scheduler.scheduleDelayed(leafProcessLocation, leafTask, delay); + scheduler.scheduleDelayed(finalLeafCenter, leafTask, delay); } // Handle replanting @@ -399,6 +462,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 @@ -409,7 +491,9 @@ private void executeTreeChop( private void processLeafRemovalWithPreCapturedSnapshot( BlockSnapshot leafSnapshot, Location centerLocation, + int radius, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, ProtectionCheckUtils.ProtectionHooks hooks, @@ -442,7 +526,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())) { @@ -455,8 +538,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); @@ -482,6 +565,7 @@ private void processLeafRemovalWithPreCapturedSnapshot( private void executeLeafRemoval( Set leavesToRemove, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, ProtectionCheckUtils.ProtectionHooks hooks, @@ -495,6 +579,12 @@ private void executeLeafRemoval( List leafList = new ArrayList<>(leavesToRemove); 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( leafList, @@ -512,11 +602,30 @@ private void executeLeafRemoval( Block leafBlock = location.getBlock(); // Remove the leaf block with all checks - removeLeafBlock(leafBlock, player, config, playerConfig, hooks); + removeLeafBlock( + leafBlock, + player, + tool, + config, + playerConfig, + hooks, + leafStatCounts, + autoPickup, + dropItems, + collectedDrops); return true; // Continue processing }, () -> { + // Flush accumulated leaf statistics + if (config.isIncrementBlockStatistics()) { + leafStatCounts.forEach((mat, count) -> player.incrementStatistic(MINE_BLOCK, mat, count)); + } + + if (autoPickup) { + DropCollectionUtils.deliverDrops(player, collectedDrops); + } + // Leaf removal complete - end session sessionManager.endLeafRemovalSession(sessionId, playerKey); }); @@ -529,9 +638,14 @@ private void executeLeafRemoval( private boolean removeLeafBlock( Block leafBlock, Player player, + ItemStack tool, Config config, PlayerConfig playerConfig, - ProtectionCheckUtils.ProtectionHooks hooks) { + ProtectionCheckUtils.ProtectionHooks hooks, + Map leafStatCounts, + boolean autoPickup, + boolean dropItems, + List collectedDrops) { Location leafLocation = leafBlock.getLocation(); @@ -567,10 +681,22 @@ private boolean removeLeafBlock( EffectUtils.showLeafRemovalEffect(player, leafBlock); } - if (config.getLeafRemovalDropItems()) { - leafBlock.breakNaturally(); - } else { + // 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) { + // 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 { + DropCollectionUtils.breakNaturally(leafBlock, tool); + } + + if (config.isIncrementBlockStatistics()) { + leafStatCounts.merge(leafMaterial, 1, Integer::sum); } // Update daily blocks count if needed 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); } /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ed7344e..2a2a911 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -83,6 +83,18 @@ 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 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, 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). +defaultAutoPickup: true # Protection plugins setting # If you are using Residence, you can set which Flag players have access to AutoTreeChop in residence. @@ -121,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 @@ -142,6 +158,7 @@ leaf-types: - PALE_OAK_LEAVES - WARPED_WART_BLOCK - NETHER_WART_BLOCK + - VINE # Replanting setting # Enable automatic sapling replanting after tree chopping diff --git a/src/main/resources/lang/de.properties b/src/main/resources/lang/de.properties index 898888c..8518a53 100644 --- a/src/main/resources/lang/de.properties +++ b/src/main/resources/lang/de.properties @@ -10,17 +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. diff --git a/src/main/resources/lang/en.properties b/src/main/resources/lang/en.properties index 3c1f47c..2520c3c 100644 --- a/src/main/resources/lang/en.properties +++ b/src/main/resources/lang/en.properties @@ -1,37 +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. consoleName=console @@ -39,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..fe2d27f 100644 --- a/src/main/resources/lang/es.properties +++ b/src/main/resources/lang/es.properties @@ -1,28 +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. diff --git a/src/main/resources/lang/fr.properties b/src/main/resources/lang/fr.properties index 560d27f..4cbf475 100644 --- a/src/main/resources/lang/fr.properties +++ b/src/main/resources/lang/fr.properties @@ -1,26 +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. diff --git a/src/main/resources/lang/it.properties b/src/main/resources/lang/it.properties index 8dfdd28..49b10e2 100644 --- a/src/main/resources/lang/it.properties +++ b/src/main/resources/lang/it.properties @@ -1,26 +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. diff --git a/src/main/resources/lang/ja.properties b/src/main/resources/lang/ja.properties index 7cd2c2b..3ca6223 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..d246ef6 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..2a04178 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/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 d1d0f47..69e9bbe 100644 --- a/src/main/resources/lang/tr.properties +++ b/src/main/resources/lang/tr.properties @@ -1,26 +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ışı. diff --git a/src/main/resources/lang/zh.properties b/src/main/resources/lang/zh.properties index ba40d33..22fabe2 100644 --- a/src/main/resources/lang/zh.properties +++ b/src/main/resources/lang/zh.properties @@ -1,37 +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=你的背包已滿,剩餘的物品掉落在你腳邊 consoleName=控制台 @@ -39,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=本伺服器已停用自動收集功能 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 23547d7..ae75752 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,6 +14,9 @@ permissions: default: true autotreechop.leaves: default: true + # Paid/VIP feature: grant it explicitly, same tier as autotreechop.vip. + autotreechop.autopickup: + default: op autotreechop.vip: default: op autotreechop.updatechecker: