Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 94 additions & 257 deletions src/main/java/org/milkteamc/autotreechop/AutoTreeChop.java

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions src/main/java/org/milkteamc/autotreechop/AutoTreeChopAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ public AutoTreeChopAPI(AutoTreeChop plugin) {
* @return boolean
*/
public boolean isAutoTreeChopEnabled(Player player) {
PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId());
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(player.getUniqueId());
return playerConfig.isAutoTreeChopEnabled();
}

/**
* Set specific player AutoTreeChop as enabled
*/
public void enableAutoTreeChop(Player player) {
PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId());
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(player.getUniqueId());
playerConfig.setAutoTreeChopEnabled(true);
}

/**
* Set specific player AutoTreeChop as disable
*/
public void disableAutoTreeChop(Player player) {
PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId());
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(player.getUniqueId());
playerConfig.setAutoTreeChopEnabled(false);
}

Expand All @@ -60,7 +60,7 @@ public void disableAutoTreeChop(Player player) {
* @return int
*/
public int getPlayerDailyUses(UUID playerUUID) {
return plugin.getPlayerDailyUses(playerUUID);
return plugin.getDataManager().getPlayerDailyUses(playerUUID);
}

/**
Expand All @@ -69,6 +69,6 @@ public int getPlayerDailyUses(UUID playerUUID) {
* @return int
*/
public int getPlayerDailyBlocksBroken(UUID playerUUID) {
return plugin.getPlayerDailyBlocksBroken(playerUUID);
return plugin.getDataManager().getPlayerDailyBlocksBroken(playerUUID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ public String onPlaceholderRequest(Player player, @NotNull String params) {
UUID playerUUID = player.getUniqueId();

if (params.equalsIgnoreCase("daily_uses")) {
return String.valueOf(plugin.getPlayerDailyUses(playerUUID));
return String.valueOf(plugin.getDataManager().getPlayerDailyUses(playerUUID));
} else if (params.equalsIgnoreCase("daily_blocks_broken")) {
return String.valueOf(plugin.getPlayerDailyBlocksBroken(playerUUID));
return String.valueOf(plugin.getDataManager().getPlayerDailyBlocksBroken(playerUUID));
} else if (params.equalsIgnoreCase("status")) {
return String.valueOf(plugin.getPlayerConfig(playerUUID).isAutoTreeChopEnabled());
return String.valueOf(
plugin.getDataManager().getPlayerConfig(playerUUID).isAutoTreeChopEnabled());
}

return null;
Expand Down
36 changes: 20 additions & 16 deletions src/main/java/org/milkteamc/autotreechop/PlayerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,56 +37,60 @@ private void checkAndUpdateDate() {
data.setDailyUses(0);
data.setDailyBlocksBroken(0);
data.setLastUseDate(LocalDate.now());
markDirty();
this.dirty = true;
}
}

public boolean isAutoTreeChopEnabled() {
public synchronized boolean isAutoTreeChopEnabled() {
return data.isAutoTreeChopEnabled();
}

public void setAutoTreeChopEnabled(boolean enabled) {
public synchronized void setAutoTreeChopEnabled(boolean enabled) {
if (data.isAutoTreeChopEnabled() != enabled) {
data.setAutoTreeChopEnabled(enabled);
markDirty();
this.dirty = true;
}
}

public int getDailyUses() {
public synchronized int getDailyUses() {
checkAndUpdateDate();
return data.getDailyUses();
}

public void incrementDailyUses() {
public synchronized void incrementDailyUses() {
checkAndUpdateDate();
data.incrementDailyUses();
markDirty();
this.dirty = true;
}

public int getDailyBlocksBroken() {
public synchronized int getDailyBlocksBroken() {
checkAndUpdateDate();
return data.getDailyBlocksBroken();
}

public void incrementDailyBlocksBroken() {
public synchronized void incrementDailyBlocksBroken() {
checkAndUpdateDate();
data.incrementDailyBlocksBroken();
markDirty();
this.dirty = true;
}

public void markDirty() {
public synchronized void markDirty() {
this.dirty = true;
}

public void clearDirty() {
this.dirty = false;
public synchronized boolean isDirty() {
return dirty;
}

public boolean isDirty() {
return dirty;
public synchronized DatabaseManager.PlayerData popSnapshotIfDirty() {
if (this.dirty) {
this.dirty = false;
return new DatabaseManager.PlayerData(this.data);
}
return null;
}

public DatabaseManager.PlayerData getData() {
public synchronized DatabaseManager.PlayerData getData() {
return data;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.milkteamc.autotreechop.Config;
import org.milkteamc.autotreechop.MessageKeys;
import org.milkteamc.autotreechop.PlayerConfig;
import org.milkteamc.autotreechop.hooks.HookManager;
import org.milkteamc.autotreechop.utils.BlockDiscoveryUtils;
import org.milkteamc.autotreechop.utils.ConfirmationManager.ChopData;
import org.milkteamc.autotreechop.utils.EffectUtils;
Expand Down Expand Up @@ -63,7 +64,7 @@ public void confirm(BukkitCommandActor actor) {
}

Config config = plugin.getPluginConfig();
PlayerConfig playerConfig = plugin.getPlayerConfig(uuid);
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(uuid);

// The block may have been broken or replaced during the confirmation window
// (e.g. another player cleared it). Re-validate before chopping.
Expand All @@ -82,15 +83,16 @@ public void confirm(BukkitCommandActor actor) {
EffectUtils.showChopEffect(player, block);
}

HookManager hookManager = plugin.getHookManager();
ProtectionHooks hooks = new ProtectionHooks(
plugin.isWorldGuardEnabled(),
plugin.getWorldGuardHook(),
plugin.isResidenceEnabled(),
plugin.getResidenceHook(),
plugin.isGriefPreventionEnabled(),
plugin.getGriefPreventionHook(),
plugin.isLandsEnabled(),
plugin.getLandsHook());
hookManager.isWorldGuardEnabled(),
hookManager.getWorldGuardHook(),
hookManager.isResidenceEnabled(),
hookManager.getResidenceHook(),
hookManager.isGriefPreventionEnabled(),
hookManager.getGriefPreventionHook(),
hookManager.isLandsEnabled(),
hookManager.getLandsHook());

plugin.getTreeChopUtils()
.chopTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void toggle(BukkitCommandActor actor, @Optional Player targetPlayer) {
}

UUID targetUUID = targetPlayer.getUniqueId();
PlayerConfig playerConfig = plugin.getPlayerConfig(targetUUID);
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(targetUUID);
boolean autoTreeChopEnabled = !playerConfig.isAutoTreeChopEnabled();
playerConfig.setAutoTreeChopEnabled(autoTreeChopEnabled);

Expand Down Expand Up @@ -96,7 +96,7 @@ public void enable(BukkitCommandActor actor) {
AutoTreeChop.sendMessage(actor.sender(), MessageKeys.ONLY_PLAYERS);
return;
}
PlayerConfig playerConfig = plugin.getPlayerConfig(player.getUniqueId());
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(player.getUniqueId());
if (playerConfig.isAutoTreeChopEnabled()) {
AutoTreeChop.sendMessage(player, MessageKeys.ALREADY_ENABLED);
return;
Expand All @@ -112,7 +112,7 @@ public void enable(BukkitCommandActor actor, EntitySelector<Player> targetPlayer
int count = 0;
String lastName = null;
for (Player targetPlayer : targetPlayers) {
PlayerConfig cfg = plugin.getPlayerConfig(targetPlayer.getUniqueId());
PlayerConfig cfg = plugin.getDataManager().getPlayerConfig(targetPlayer.getUniqueId());
if (cfg.isAutoTreeChopEnabled()) continue; // skip already-enabled silently, or send per-player msg
cfg.setAutoTreeChopEnabled(true);
lastName = targetPlayer.getName();
Expand Down Expand Up @@ -144,7 +144,7 @@ public void disable(BukkitCommandActor actor) {
return;
}
UUID playerUUID = player.getUniqueId();
PlayerConfig playerConfig = plugin.getPlayerConfig(playerUUID);
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(playerUUID);
if (!playerConfig.isAutoTreeChopEnabled()) {
AutoTreeChop.sendMessage(player, MessageKeys.ALREADY_DISABLED);
return;
Expand All @@ -162,7 +162,7 @@ public void disable(BukkitCommandActor actor, EntitySelector<Player> targetPlaye
String lastName = null;
for (Player targetPlayer : targetPlayers) {
UUID targetUUID = targetPlayer.getUniqueId();
PlayerConfig cfg = plugin.getPlayerConfig(targetUUID);
PlayerConfig cfg = plugin.getDataManager().getPlayerConfig(targetUUID);
if (!cfg.isAutoTreeChopEnabled()) continue;
cfg.setAutoTreeChopEnabled(false);
plugin.getConfirmationManager().clearPlayer(targetUUID);
Expand Down Expand Up @@ -194,7 +194,7 @@ private void performSelfToggle(BukkitCommandActor actor) {
}

UUID playerUUID = player.getUniqueId();
PlayerConfig playerConfig = plugin.getPlayerConfig(playerUUID);
PlayerConfig playerConfig = plugin.getDataManager().getPlayerConfig(playerUUID);
boolean autoTreeChopEnabled = !playerConfig.isAutoTreeChopEnabled();
playerConfig.setAutoTreeChopEnabled(autoTreeChopEnabled);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public void usage(BukkitCommandActor actor) {
}

Player player = actor.asPlayer();
org.milkteamc.autotreechop.PlayerConfig pConfig = plugin.getPlayerConfig(player.getUniqueId());
org.milkteamc.autotreechop.PlayerConfig pConfig =
plugin.getDataManager().getPlayerConfig(player.getUniqueId());

boolean isVip = player.hasPermission("autotreechop.vip");
boolean limitVip = config.getLimitVipUsage();
Expand Down
130 changes: 130 additions & 0 deletions src/main/java/org/milkteamc/autotreechop/database/DataManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package org.milkteamc.autotreechop.database;

import com.github.Anon8281.universalScheduler.UniversalScheduler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.milkteamc.autotreechop.AutoTreeChop;
import org.milkteamc.autotreechop.PlayerConfig;
import org.milkteamc.autotreechop.tasks.PlayerDataSaveTask;
import org.milkteamc.autotreechop.utils.ConfirmationManager;
import org.milkteamc.autotreechop.utils.SessionManager;

public class DataManager {

private static final long SAVE_INTERVAL = 1200L; // 60s
private static final int SAVE_THRESHOLD = 15;

private final AutoTreeChop plugin;
private final DatabaseManager databaseManager;
private final ConfirmationManager confirmationManager;
private final Map<UUID, PlayerConfig> playerConfigs = new ConcurrentHashMap<>();

private PlayerDataSaveTask saveTask;

public DataManager(AutoTreeChop plugin, DatabaseManager databaseManager, ConfirmationManager confirmationManager) {
this.plugin = plugin;
this.databaseManager = databaseManager;
this.confirmationManager = confirmationManager;
}

public void startSaveTask() {
this.saveTask = new PlayerDataSaveTask(plugin, SAVE_THRESHOLD);
UniversalScheduler.getScheduler(plugin).runTaskTimerAsynchronously(saveTask, SAVE_INTERVAL, SAVE_INTERVAL);
}

public void shutdown() {
plugin.getLogger().info("Saving all player data before shutdown...");

if (saveTask != null) {
try {
saveTask.cancel();
} catch (IllegalStateException ignored) {
// Task was never scheduled or already cancelled (e.g. Folia shutdown)
}
}

if (!playerConfigs.isEmpty()) {
SessionManager sessionManager = SessionManager.getInstance();
List<DatabaseManager.PlayerData> dirtyDataList = new ArrayList<>();

for (Map.Entry<UUID, PlayerConfig> entry : playerConfigs.entrySet()) {
UUID uuid = entry.getKey();
PlayerConfig pConfig = entry.getValue();

if (confirmationManager != null) {
confirmationManager.clearPlayer(uuid);
}

if (sessionManager != null) {
sessionManager.clearAllPlayerSessions(uuid);
}

DatabaseManager.PlayerData snapshot = pConfig.popSnapshotIfDirty();
if (snapshot != null) {
dirtyDataList.add(snapshot);
}
}

if (!dirtyDataList.isEmpty() && databaseManager != null) {
long startTime = System.currentTimeMillis();
databaseManager.savePlayerDataBatchSync(dirtyDataList);
long duration = System.currentTimeMillis() - startTime;
plugin.getLogger()
.info("Successfully saved " + dirtyDataList.size() + " player records in " + duration + "ms.");
}

playerConfigs.clear();
}

if (databaseManager != null) {
databaseManager.close();
}
}

public void addPlayerConfig(UUID uuid, PlayerConfig config) {
playerConfigs.put(uuid, config);
}

public PlayerConfig removePlayerConfig(UUID uuid) {
return playerConfigs.remove(uuid);
}

public PlayerConfig getPlayerConfig(UUID uuid) {
return playerConfigs.get(uuid);
}

public Collection<PlayerConfig> getOnlinePlayersConfigs() {
return playerConfigs.values();
}

public int getPlayerDailyUses(UUID playerUUID) {
PlayerConfig config = getPlayerConfig(playerUUID);
return config != null ? config.getDailyUses() : 0;
}

public int getPlayerDailyBlocksBroken(UUID playerUUID) {
PlayerConfig config = getPlayerConfig(playerUUID);
return config != null ? config.getDailyBlocksBroken() : 0;
}
}
Loading