From 4aa29a348bec55a5039b98f3c397c5f1bcbe2bb6 Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Mon, 13 Jul 2026 08:05:10 +0200 Subject: [PATCH 01/11] fix: MiniMessage support for PlaceholderAPI placeholders --- .gitignore | 1 + .../gyvex/ezafk/compatibility/LoreUtil.java | 4 +- .../gyvex/ezafk/manager/MessageManager.java | 4 +- .../MiniMessagePlaceholderFeatureTest.java | 96 +++++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/gyvex/ezafk/compatibility/MiniMessagePlaceholderFeatureTest.java diff --git a/.gitignore b/.gitignore index b425f09..75d9a06 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ target/ .factorypath .project .settings +.kilo .springBeans .sts4-cache diff --git a/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java b/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java index 28bd28d..bd988d1 100644 --- a/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java +++ b/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java @@ -69,13 +69,13 @@ public static boolean validateMiniMessage(String miniMessageString, String conte /** * Converts a raw message string to a legacy §-color string suitable for - * {@link ItemMeta#setDisplayName} / {@link ItemMeta#setLore}. + * {@link ItemMeta#setDisplayName} / {@link ItemMeta#setLore} and chat messages. * *

When the bundled MiniMessage library is available (always, since it is shaded into the * JAR), MiniMessage tags such as {@code } are parsed and the result is serialized back * to a legacy color string. Otherwise, {@code &}-color codes are translated directly.

*/ - private static String toDisplayString(String text, Logger logger) { + public static String toDisplayString(String text, Logger logger) { try { Object component = deserializeToComponent(text); if (component != null) { diff --git a/src/main/java/com/gyvex/ezafk/manager/MessageManager.java b/src/main/java/com/gyvex/ezafk/manager/MessageManager.java index 13979b4..8377b4a 100644 --- a/src/main/java/com/gyvex/ezafk/manager/MessageManager.java +++ b/src/main/java/com/gyvex/ezafk/manager/MessageManager.java @@ -2,9 +2,9 @@ import com.gyvex.ezafk.EzAfk; import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.LoreUtil; import com.gyvex.ezafk.state.AfkState; import com.gyvex.ezafk.util.PlaceholderUtil; -import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -71,7 +71,7 @@ public static String getMessage(String path, String fallback, MapHello World", + null, + player + ); + + assertNotNull(result); + assertTrue(result.contains(SECTION + "c"), "MiniMessage should become §c"); + assertTrue(result.contains(SECTION + "l"), "MiniMessage should become §l"); + } + + @Test + void getMessage_parses_minimessage_tags_with_legacy_colors() { + PlayerMock player = (PlayerMock) server.addPlayer("MixedPlayer"); + + String result = MessageManager.getMessage( + "nonexistent.path", + "&e[yellow] %player% &7[is AFK]", + null, + player + ); + + assertNotNull(result); + assertTrue(result.contains(SECTION + "e"), "Amp color codes should be translated"); + assertTrue(result.contains(SECTION + "c"), "MiniMessage should become §c"); + assertTrue(result.contains(SECTION + "7"), "Amp color codes should be translated"); + assertTrue(result.contains("%player%"), "PAPI placeholders remain when PAPI unavailable"); + } + + @Test + void getMessage_handles_plain_text_without_colors() { + PlayerMock player = (PlayerMock) server.addPlayer("PlainPlayer"); + + String result = MessageManager.getMessage( + "nonexistent.path", + "Player %player% is now AFK", + null, + player + ); + + assertNotNull(result); + assertEquals("Player %player% is now AFK", result); + } + + @Test + void getMessage_handles_only_legacy_amp_colors() { + PlayerMock player = (PlayerMock) server.addPlayer("ColorPlayer"); + + String result = MessageManager.getMessage( + "nonexistent.path", + "&c&lPlayer &7%player% &cis no longer AFK", + null, + player + ); + + assertNotNull(result); + assertTrue(result.contains(SECTION + "c"), "Should contain §c"); + assertTrue(result.contains(SECTION + "l"), "Should contain §l for bold"); + assertTrue(result.contains(SECTION + "7"), "Should contain §7"); + } +} \ No newline at end of file From a4562e93cabf03a47e04a373c7415df240a75c2c Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Mon, 13 Jul 2026 08:46:24 +0200 Subject: [PATCH 02/11] fix: updated MiniMessage dep --- pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ba8fe37..dfbe4af 100644 --- a/pom.xml +++ b/pom.xml @@ -155,7 +155,7 @@ org.bstats bstats-bukkit - + org.apache.logging.log4j log4j-api @@ -170,22 +170,22 @@ net.kyori adventure-api - 4.26.1 + 5.2.0 net.kyori adventure-key - 4.26.1 + 5.2.0 net.kyori adventure-text-minimessage - 4.26.1 + 5.2.0 net.kyori adventure-text-serializer-legacy - 4.26.1 + 5.2.0 net.kyori @@ -284,7 +284,7 @@ me.clip placeholderapi - 2.11.5 + 2.12.2 provided From d6d737979b56e410a49ce8d3b3a8e2257ba3e750 Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Mon, 13 Jul 2026 09:44:02 +0200 Subject: [PATCH 03/11] fix: updated MiniMessage version --- .mvn/maven.config | 1 - pom.xml | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.mvn/maven.config b/.mvn/maven.config index 4961335..e69de29 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1 +0,0 @@ --Dmockbukkit.version=4.113.1 diff --git a/pom.xml b/pom.xml index dfbe4af..95e68b5 100644 --- a/pom.xml +++ b/pom.xml @@ -155,7 +155,7 @@ org.bstats bstats-bukkit - + org.apache.logging.log4j log4j-api @@ -436,13 +436,14 @@ - + org.apache.maven.plugins maven-surefire-plugin 3.5.5 + ${maven.test.skip} false - ${argLine} -Dnet.bytebuddy.experimental=true + -Dnet.bytebuddy.experimental=true From 3401652cbdfbbffdadbcc767e4d6b28c39b589af Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Wed, 15 Jul 2026 15:32:46 +0200 Subject: [PATCH 04/11] feat: refactored plugin to multi module setup --- ezafk-api/pom.xml | 45 ++ .../gyvex/ezafk/version/VersionAdapter.java | 14 + ezafk-core/pom.xml | 241 ++++++++ .../src/main/java/com/gyvex/ezafk/EzAfk.java | 37 ++ .../com/gyvex/ezafk/bootstrap/Bootstrap.java | 256 ++++++++ .../com/gyvex/ezafk/bootstrap/Registry.java | 193 ++++++ .../gyvex/ezafk/command/AfkZoneCommand.java | 299 ++++++++++ .../com/gyvex/ezafk/command/EzAfkCommand.java | 484 +++++++++++++++ .../ezafk/command/EzAfkTabCompleter.java | 132 +++++ .../gyvex/ezafk/compatibility/LoreUtil.java | 115 ++++ .../ezafk/compatibility/ParticleCompat.java | 49 ++ .../inventory/InventoryCompat.java | 40 ++ .../ezafk/compatibility/item/HeadCompat.java | 89 +++ .../compatibility/item/ItemMetaCompat.java | 139 +++++ .../compatibility/item/MaterialCompat.java | 45 ++ .../platform/PlatformDetector.java | 28 + .../player/PlayerDisplayCompat.java | 95 +++ .../player/PlayerKickCompat.java | 42 ++ .../scheduler/BukkitSchedulerAdapter.java | 50 ++ .../scheduler/SchedulerAdapter.java | 21 + .../scheduler/SchedulerAdapterFactory.java | 42 ++ .../compatibility/scheduler/TaskHandle.java | 7 + .../compatibility/sound/SoundCompat.java | 48 ++ .../com/gyvex/ezafk/config/ConfigManager.java | 264 +++++++++ .../event/PlayerAfkStatusChangeEvent.java | 69 +++ .../java/com/gyvex/ezafk/gui/GuiAction.java | 96 +++ .../com/gyvex/ezafk/gui/GuiActionFactory.java | 31 + .../ezafk/integration/EconomyIntegration.java | 79 +++ .../gyvex/ezafk/integration/Integration.java | 7 + .../ezafk/integration/MetricsIntegration.java | 42 ++ .../PlaceholderApiIntegration.java | 43 ++ .../integration/PlayerListNameAdapter.java | 16 + .../SimpleVoiceChatIntegration.java | 5 + .../ezafk/integration/SpigotIntegration.java | 36 ++ .../ezafk/integration/TabIntegration.java | 561 ++++++++++++++++++ .../integration/VoiceChatIntegration.java | 25 + .../integration/WorldEditIntegration.java | 136 +++++ .../integration/WorldGuardIntegration.java | 118 ++++ .../ezcountdown/EzCountdownIntegration.java | 300 ++++++++++ .../EzAfkPlaceholderExpansion.java | 231 ++++++++ .../integration/spigot/UpdateChecker.java | 32 + .../tab/PlayerListNameAdapter.java | 16 + .../tab/TabApiPlayerListNameAdapter.java | 345 +++++++++++ .../voicechat/EzAfkVoicechatPlugin.java | 28 + .../EzAfkVoicechatServiceRegistrar.java | 13 + .../voicechat/SimpleVoiceChatIntegration.java | 169 ++++++ .../worldedit/LegacyWorldEditHelper.java | 88 +++ .../worldedit/WorldEditHelper.java | 138 +++++ .../integration/worldguard/FlagRegistrar.java | 87 +++ .../worldguard/WorldGuardSupport.java | 57 ++ .../worldguard/flag/AfkBypassFlag.java | 52 ++ .../ezafk/listener/AfkPlayerActionsGUI.java | 275 +++++++++ .../ezafk/listener/AfkPlayerOverviewGUI.java | 354 +++++++++++ .../listener/EconomyServiceListener.java | 83 +++ .../gyvex/ezafk/listener/MoveListener.java | 246 ++++++++ .../listener/PlayerActivityListener.java | 48 ++ .../ezafk/listener/PlayerQuitListener.java | 43 ++ .../listener/SimpleVoiceChatAfkListener.java | 26 + .../gyvex/ezafk/manager/AfkTimeManager.java | 437 ++++++++++++++ .../gyvex/ezafk/manager/AfkZoneManager.java | 221 +++++++ .../ezafk/manager/AfkZoneRewardManager.java | 363 ++++++++++++ .../com/gyvex/ezafk/manager/BypassList.java | 77 +++ .../ezafk/manager/BypassListManager.java | 119 ++++ .../gyvex/ezafk/manager/EconomyManager.java | 397 +++++++++++++ .../ezafk/manager/IntegrationManager.java | 59 ++ .../gyvex/ezafk/manager/MessageManager.java | 91 +++ .../com/gyvex/ezafk/manager/MySQLManager.java | 418 +++++++++++++ .../ezafk/manager/ZoneRewardStatsManager.java | 126 ++++ .../gyvex/ezafk/repository/AfkTimeModel.java | 34 ++ .../ezafk/repository/StorageFactory.java | 36 ++ .../ezafk/repository/StorageRepository.java | 17 + .../ezafk/repository/mysql/MySQLStorage.java | 155 +++++ .../repository/sqlite/SQLiteStorage.java | 138 +++++ .../ezafk/repository/yaml/YamlDataStore.java | 152 +++++ .../ezafk/repository/yaml/YamlStorage.java | 95 +++ .../gyvex/ezafk/state/AfkActivationMode.java | 18 + .../java/com/gyvex/ezafk/state/AfkReason.java | 24 + .../java/com/gyvex/ezafk/state/AfkState.java | 414 +++++++++++++ .../gyvex/ezafk/state/AfkStatusDetails.java | 34 ++ .../gyvex/ezafk/state/LastActiveState.java | 34 ++ .../com/gyvex/ezafk/state/StateAnimator.java | 48 ++ .../com/gyvex/ezafk/state/ToggleResult.java | 13 + .../com/gyvex/ezafk/task/AfkCheckTask.java | 331 +++++++++++ .../com/gyvex/ezafk/task/TaskManager.java | 20 + .../com/gyvex/ezafk/util/CommandUtil.java | 28 + .../gyvex/ezafk/util/DurationFormatter.java | 42 ++ .../com/gyvex/ezafk/util/PlaceholderUtil.java | 154 +++++ .../ezafk/version/FallbackVersionAdapter.java | 51 ++ .../ezafk/version/VersionAdapterFactory.java | 57 ++ .../main/java/com/gyvex/ezafk/zone/Zone.java | 82 +++ .../java/com/gyvex/ezafk/zone/ZoneCache.java | 44 ++ ezafk-core/src/main/resources/config.yml | 210 +++++++ ezafk-core/src/main/resources/gui.yml | 49 ++ .../src/main/resources/messages/messages.yml | 142 +++++ .../main/resources/messages/messages_de.yml | 108 ++++ .../main/resources/messages/messages_en.yml | 109 ++++ .../main/resources/messages/messages_es.yml | 98 +++ .../main/resources/messages/messages_ja.yml | 109 ++++ .../main/resources/messages/messages_nl.yml | 99 ++++ .../main/resources/messages/messages_ru.yml | 100 ++++ .../main/resources/messages/messages_zh.yml | 101 ++++ .../src/main/resources/mp3/ezafk-sound.mp3 | Bin 0 -> 720000 bytes ezafk-core/src/main/resources/mysql.yml | 7 + ezafk-core/src/main/resources/plugin.yml | 69 +++ ezafk-core/src/main/resources/zones.yml | 113 ++++ ezafk-folia/pom.xml | 44 ++ .../scheduler/FoliaSchedulerAdapter.java | 64 ++ ezafk-paper-26.1/pom.xml | 44 ++ .../version/Paper261PlusVersionAdapter.java | 28 + ezafk/pom.xml | 128 ++++ 110 files changed, 12621 insertions(+) create mode 100644 ezafk-api/pom.xml create mode 100644 ezafk-api/src/main/java/com/gyvex/ezafk/version/VersionAdapter.java create mode 100644 ezafk-core/pom.xml create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/EzAfk.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Bootstrap.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Registry.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/command/AfkZoneCommand.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkCommand.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkTabCompleter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/ParticleCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/inventory/InventoryCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/HeadCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/ItemMetaCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/MaterialCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/platform/PlatformDetector.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerDisplayCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerKickCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/BukkitSchedulerAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapterFactory.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/TaskHandle.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/sound/SoundCompat.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/config/ConfigManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/event/PlayerAfkStatusChangeEvent.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiAction.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiActionFactory.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/EconomyIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/Integration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/MetricsIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlaceholderApiIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlayerListNameAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/SimpleVoiceChatIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/SpigotIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/TabIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/VoiceChatIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldEditIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldGuardIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/ezcountdown/EzCountdownIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/placeholder/EzAfkPlaceholderExpansion.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/spigot/UpdateChecker.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/PlayerListNameAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/TabApiPlayerListNameAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatPlugin.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatServiceRegistrar.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/SimpleVoiceChatIntegration.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/LegacyWorldEditHelper.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/WorldEditHelper.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrar.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/WorldGuardSupport.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/flag/AfkBypassFlag.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerActionsGUI.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerOverviewGUI.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/EconomyServiceListener.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/MoveListener.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerActivityListener.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerQuitListener.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/listener/SimpleVoiceChatAfkListener.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkTimeManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneRewardManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassList.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassListManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/EconomyManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/IntegrationManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/MessageManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/MySQLManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/manager/ZoneRewardStatsManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/AfkTimeModel.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageFactory.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageRepository.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/mysql/MySQLStorage.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/sqlite/SQLiteStorage.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlDataStore.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlStorage.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkActivationMode.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkReason.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkState.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkStatusDetails.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/LastActiveState.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/StateAnimator.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/state/ToggleResult.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/task/AfkCheckTask.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/task/TaskManager.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/util/CommandUtil.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/util/DurationFormatter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/util/PlaceholderUtil.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/version/FallbackVersionAdapter.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/version/VersionAdapterFactory.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/zone/Zone.java create mode 100644 ezafk-core/src/main/java/com/gyvex/ezafk/zone/ZoneCache.java create mode 100644 ezafk-core/src/main/resources/config.yml create mode 100644 ezafk-core/src/main/resources/gui.yml create mode 100644 ezafk-core/src/main/resources/messages/messages.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_de.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_en.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_es.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_ja.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_nl.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_ru.yml create mode 100644 ezafk-core/src/main/resources/messages/messages_zh.yml create mode 100644 ezafk-core/src/main/resources/mp3/ezafk-sound.mp3 create mode 100644 ezafk-core/src/main/resources/mysql.yml create mode 100644 ezafk-core/src/main/resources/plugin.yml create mode 100644 ezafk-core/src/main/resources/zones.yml create mode 100644 ezafk-folia/pom.xml create mode 100644 ezafk-folia/src/main/java/com/gyvex/ezafk/compatibility/scheduler/FoliaSchedulerAdapter.java create mode 100644 ezafk-paper-26.1/pom.xml create mode 100644 ezafk-paper-26.1/src/main/java/com/gyvex/ezafk/version/Paper261PlusVersionAdapter.java create mode 100644 ezafk/pom.xml diff --git a/ezafk-api/pom.xml b/ezafk-api/pom.xml new file mode 100644 index 0000000..b5825f0 --- /dev/null +++ b/ezafk-api/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + com.gyvex + ezafk-parent + 3.1.1 + + + ezafk-api + jar + EzAfk API + + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + org.jetbrains + annotations + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + true + + + + + \ No newline at end of file diff --git a/ezafk-api/src/main/java/com/gyvex/ezafk/version/VersionAdapter.java b/ezafk-api/src/main/java/com/gyvex/ezafk/version/VersionAdapter.java new file mode 100644 index 0000000..2d6f576 --- /dev/null +++ b/ezafk-api/src/main/java/com/gyvex/ezafk/version/VersionAdapter.java @@ -0,0 +1,14 @@ +package com.gyvex.ezafk.version; + +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.potion.PotionEffectType; + +public interface VersionAdapter { + + Attribute getMaxHealthAttribute(); + + PotionEffectType getEffect(String minecraftKey); + + Enchantment getEnchantment(String minecraftKey); +} \ No newline at end of file diff --git a/ezafk-core/pom.xml b/ezafk-core/pom.xml new file mode 100644 index 0000000..658b75c --- /dev/null +++ b/ezafk-core/pom.xml @@ -0,0 +1,241 @@ + + + 4.0.0 + + + com.gyvex + ezafk-parent + 3.1.1 + + + ezafk-core + jar + EzAfk Core + + + + com.gyvex + ezafk-api + ${project.version} + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + com.google.code.gson + gson + + + org.apache.commons + commons-lang3 + + + org.slf4j + slf4j-api + + + org.jetbrains + annotations + + + + + com.github.MilkBowl + VaultAPI + 1.7.1 + provided + + + me.clip + placeholderapi + 2.12.2 + provided + + + org.jetbrains + annotations + + + + + com.sk89q.worldguard + worldguard-bukkit + ${worldguard.version} + provided + + + org.bstats + bstats-bukkit + + + org.bstats + bstats-base + + + net.kyori + adventure-api + + + + + com.github.NEZNAMY + TAB-API + 5.5.0 + provided + + + org.jetbrains + annotations + + + + + com.google.code.gson + gson + 2.11.0 + provided + + + org.apache.logging.log4j + log4j-api + ${log4j.api.version} + provided + + + org.slf4j + slf4j-api + 2.0.17 + provided + + + org.bstats + bstats-bukkit + ${bstats.version} + compile + + + net.kyori + adventure-api + 5.2.0 + + + net.kyori + adventure-key + 5.2.0 + + + net.kyori + adventure-text-minimessage + 5.2.0 + + + net.kyori + adventure-text-serializer-legacy + 5.2.0 + + + net.kyori + examination-api + 1.3.0 + + + org.jetbrains + annotations + + + + + net.kyori + examination-string + 1.3.0 + + + org.jetbrains + annotations + + + + + javazoom + jlayer + 1.0.1 + + + de.maxhenkel.voicechat + voicechat-api + 2.2.5 + provided + + + com.github.EzFramework + Jaloquent + ${jaloquent.version} + compile + + + com.github.ez-plugins + ezcountdown + 2.0.1 + provided + + + io.papermc.paper + paper-api + + + net.kyori + adventure-api + + + net.kyori + adventure-key + + + net.kyori + adventure-text-minimessage + + + net.kyori + adventure-text-serializer-legacy + + + org.bstats + bstats-bukkit + + + org.bstats + bstats-base + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + true + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + ${maven.test.skip} + false + -Dnet.bytebuddy.experimental=true + + + + + \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/EzAfk.java b/ezafk-core/src/main/java/com/gyvex/ezafk/EzAfk.java new file mode 100644 index 0000000..0bad08b --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/EzAfk.java @@ -0,0 +1,37 @@ +package com.gyvex.ezafk; + +import org.bukkit.plugin.java.JavaPlugin; + +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.integration.worldguard.WorldGuardSupport; + +public class EzAfk extends JavaPlugin { + @Override + public void onLoad() { + // Only attempt early WorldGuard flag registration here. Keep heavy + // Registry initialization for onEnable so startup is safer. + try { + WorldGuardSupport.registerFlagsIfPossible(this, this.getLogger()); + } catch (Throwable ignored) { + } + } + + @Override + public void onEnable() { + // Initialize Registry and run both load-phase and enable-phase bootstrap now. + Registry.init(this); + Registry.get().getBootstrap().onLoad(); + Registry.get().getBootstrap().onEnable(); + } + + @Override + public void onDisable() { + try { + if (Registry.get().getBootstrap() != null) Registry.get().getBootstrap().onDisable(); + } catch (IllegalStateException ignored) { + // Registry not initialized + } finally { + Registry.reset(); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Bootstrap.java b/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Bootstrap.java new file mode 100644 index 0000000..dee59bb --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Bootstrap.java @@ -0,0 +1,256 @@ +package com.gyvex.ezafk.bootstrap; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.command.EzAfkCommand; +import com.gyvex.ezafk.command.EzAfkTabCompleter; +import com.gyvex.ezafk.listener.MoveListener; +import com.gyvex.ezafk.listener.PlayerActivityListener; +import com.gyvex.ezafk.listener.PlayerQuitListener; +import com.gyvex.ezafk.listener.AfkPlayerActionsGUI; +import com.gyvex.ezafk.listener.AfkPlayerOverviewGUI; +import com.gyvex.ezafk.integration.EconomyIntegration; +import com.gyvex.ezafk.integration.MetricsIntegration; +import com.gyvex.ezafk.integration.PlaceholderApiIntegration; +import com.gyvex.ezafk.listener.SimpleVoiceChatAfkListener; +import com.gyvex.ezafk.integration.SpigotIntegration; +import com.gyvex.ezafk.integration.TabIntegration; +import com.gyvex.ezafk.integration.VoiceChatIntegration; +import com.gyvex.ezafk.integration.WorldEditIntegration; +import com.gyvex.ezafk.integration.WorldGuardIntegration; +import com.gyvex.ezafk.integration.ezcountdown.EzCountdownIntegration; +import com.gyvex.ezafk.manager.IntegrationManager; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.manager.EconomyManager; +import com.gyvex.ezafk.listener.EconomyServiceListener; +import com.gyvex.ezafk.manager.AfkTimeManager; +import com.gyvex.ezafk.manager.BypassListManager; +import com.gyvex.ezafk.task.TaskManager; +import com.github.ezframework.jaloquent.config.JaloquentConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +import java.util.ArrayList; +import java.util.UUID; +import java.util.logging.Level; + +public class Bootstrap { + private final EzAfk plugin; + private final ArrayList registeredListeners = new ArrayList<>(); + private TaskManager taskManager = null; + private com.gyvex.ezafk.listener.EconomyServiceListener economyServiceListener; + + public Bootstrap(EzAfk plugin) { + this.plugin = plugin; + } + + public void onLoad() { + plugin.saveDefaultConfig(); + Registry.get().getConfigManager().loadConfig(); + maybeRegisterWorldGuardIntegration(); + maybeRegisterWorldEditIntegration(); + } + + public void onEnable() { + Registry.get().getConfigManager().loadConfig(); + JaloquentConfig.enableLogging(Registry.get().getConfigManager().isDebugEnabled()); + logStartupBanner(); + // Copy default AFK sound to the EzAfk plugin folder (plugins/EzAfk/mp3/ezafk-sound.mp3) + String afkSoundPath = Registry.get().getConfigManager().getAfkSoundFile(); + java.io.File afkSoundFile = new java.io.File(Registry.get().getPlugin().getDataFolder(), afkSoundPath); // plugins/EzAfk/mp3/ezafk-sound.mp3 + // Use Bukkit's saveResource to copy the mp3 file safely + plugin.saveResource("mp3/ezafk-sound.mp3", true); + + IntegrationManager.addIntegration("metrics", new MetricsIntegration()); + IntegrationManager.addIntegration("tab", new TabIntegration()); + IntegrationManager.addIntegration("spigot", new SpigotIntegration()); + IntegrationManager.addIntegration("economy", new EconomyIntegration()); + IntegrationManager.addIntegration("placeholderapi", new PlaceholderApiIntegration()); + + String voicechatConfig = plugin.getConfig().getString("integration.voicechat", "auto").trim().toLowerCase(); + boolean voicechatAvailable = plugin.getServer().getPluginManager().getPlugin("voicechat") != null; + boolean enableVoicechatIntegration = false; + if ("true".equals(voicechatConfig)) { + enableVoicechatIntegration = true; + } else if ("auto".equals(voicechatConfig)) { + enableVoicechatIntegration = voicechatAvailable; + } // false disables integration + + if (enableVoicechatIntegration) { + IntegrationManager.addIntegration("voicechat", new VoiceChatIntegration(plugin)); + } + + String ezcountdownConfig = plugin.getConfig().getString("integration.ezcountdown", "auto").trim().toLowerCase(); + boolean ezcountdownAvailable = plugin.getServer().getPluginManager().getPlugin("EzCountdown") != null; + boolean enableEzCountdown = "true".equals(ezcountdownConfig) + || ("auto".equals(ezcountdownConfig) && ezcountdownAvailable); + if (enableEzCountdown) { + IntegrationManager.addIntegration("ezcountdown", new EzCountdownIntegration()); + } + + IntegrationManager.load(); + + String storageType = plugin.getConfig().getString("storage.type", "yaml").trim().toLowerCase(); + plugin.getLogger().fine("Storage type selected: " + storageType); + // Storage repository is initialized during Registry.init(); on reload the command will refresh it. + AfkTimeManager.load(plugin); + BypassListManager.load(plugin); + + if (plugin.getConfig().getBoolean("economy.enabled", false)) { + economyServiceListener = new EconomyServiceListener(); + try { + plugin.getServer().getPluginManager().registerEvents(economyServiceListener, plugin); + } catch (org.bukkit.plugin.IllegalPluginAccessException ex) { + plugin.getLogger().warning("Failed to register EconomyServiceListener during enable: " + ex.getMessage()); + } + } + + registerListener(new MoveListener(plugin)); + registerListener(new PlayerActivityListener()); + registerListener(new AfkPlayerOverviewGUI()); + registerListener(new AfkPlayerActionsGUI()); + registerListener(new PlayerQuitListener()); + + // Register Simple Voice Chat AFK listener + if (enableVoicechatIntegration) { + registerListener(new SimpleVoiceChatAfkListener(plugin)); + } + + if (plugin.getCommand("ezafk") != null) { + plugin.getCommand("ezafk").setExecutor(new EzAfkCommand(plugin)); + plugin.getCommand("ezafk").setTabCompleter(new EzAfkTabCompleter()); + } else { + plugin.getLogger().warning("Command 'ezafk' not found in plugin description; skipping command registration."); + } + + // Initialize and start task manager here to avoid loading task classes during onLoad/init. + this.taskManager = new TaskManager(); + taskManager.startAfkCheckTask(plugin); + } + + private void logStartupBanner() { + String[] banner = new String[]{ + "███████╗███████╗░█████╗░███████╗██╗░░██╗", + "██╔════╝╚════██║██╔══██╗██╔════╝██║░██╔╝", + "█████╗░░░░███╔═╝███████║█████╗░░█████═╝░", + "██╔══╝░░██╔══╝░░██╔══██║██╔══╝░░██╔═██╗░", + "███████╗███████╗██║░░██║██║░░░░░██║░╚██╗", + "╚══════╝╚══════╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░╚═╝" + }; + + for (String line : banner) { + plugin.getLogger().info(line); + } + } + + public void onDisable() { + AfkTimeManager.flushActiveSessions(AfkState.getActiveAfkSessions()); + if (plugin.getConfig().getBoolean("afk.hide-screen.enabled")) { + for (UUID uuid : new ArrayList<>(AfkState.afkPlayers)) { + Player player = Bukkit.getPlayer(uuid); + + if (player != null) { + player.removePotionEffect(PotionEffectType.BLINDNESS); + } + } + } + + AfkState.restoreAllDisplayNames(); + + AfkState.afkPlayers.clear(); + AfkState.clearBypass(); + + // Unregister all listeners + for (Listener listener : registeredListeners) { + HandlerList.unregisterAll(listener); + } + registeredListeners.clear(); + + if (economyServiceListener != null) { + HandlerList.unregisterAll(economyServiceListener); + economyServiceListener = null; + } + + if (taskManager != null) { + taskManager.cancelTasks(); + taskManager = null; + } + + IntegrationManager.unload(); + EconomyManager.reset(); + AfkTimeManager.shutdown(); + } + + private void registerListener(Listener listener) { + try { + plugin.getServer().getPluginManager().registerEvents(listener, plugin); + registeredListeners.add(listener); + } catch (org.bukkit.plugin.IllegalPluginAccessException ex) { + plugin.getLogger().warning("Failed to register listener " + listener.getClass().getSimpleName() + " during enable: " + ex.getMessage()); + } + } + + private void maybeRegisterWorldGuardIntegration() { + + if (!plugin.getConfig().getBoolean("integration.worldguard")) { + plugin.getLogger().fine("WorldGuard integration disabled via config"); + return; + } + + org.bukkit.plugin.Plugin worldGuardPlugin = plugin.getServer().getPluginManager().getPlugin("WorldGuard"); + + if (worldGuardPlugin == null || !worldGuardPlugin.isEnabled()) { + plugin.getLogger().info("WorldGuard plugin not found. Attempting class-based detection and flag registration."); + // Continue: even if the PluginManager does not report WorldGuard, try + // class-based detection and flag registration. This covers cases where + // classes are present on the classpath or WorldGuard is loaded differently. + } + + // Create and register the integration instance now, but defer calling + // its `load()` method until IntegrationManager.load() runs in onEnable(). + // This prevents double-invocation of setup logic. + try { + // Allow test/runtime overrides to block registration + // (IntegrationManager.setRegistrationAllowed can be used by tests) + if (!IntegrationManager.isRegistrationAllowed("worldguard")) { + plugin.getLogger().info("WorldGuard integration explicitly disabled by test override."); + return; + } + WorldGuardIntegration integration = new WorldGuardIntegration(); + IntegrationManager.addIntegration("worldguard", integration); + plugin.getLogger().info("WorldGuard integration registered."); + } catch (NoClassDefFoundError ex) { + plugin.getLogger().log(Level.WARNING, "Failed to initialize WorldGuard integration.", ex); + } + } + + private void maybeRegisterWorldEditIntegration() { + if (!plugin.getConfig().getBoolean("integration.worldedit", true)) { + plugin.getLogger().info("WorldEdit integration disabled via config"); + return; + } + + org.bukkit.plugin.Plugin wePlugin = plugin.getServer().getPluginManager().getPlugin("WorldEdit"); + + if (wePlugin == null || !wePlugin.isEnabled()) { + plugin.getLogger().info("WorldEdit plugin not found. Skipping integration setup."); + return; + } + + try { + WorldEditIntegration integration = new WorldEditIntegration(); + integration.load(); + + if (integration.isSetup) { + IntegrationManager.addIntegration("worldedit", integration); + plugin.getLogger().info("WorldEdit integration registered."); + } else { + plugin.getLogger().info("WorldEdit integration setup skipped."); + } + } catch (NoClassDefFoundError ex) { + plugin.getLogger().log(java.util.logging.Level.WARNING, "Failed to initialize WorldEdit integration.", ex); + } + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Registry.java b/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Registry.java new file mode 100644 index 0000000..df8b71a --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/bootstrap/Registry.java @@ -0,0 +1,193 @@ +package com.gyvex.ezafk.bootstrap; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.compatibility.scheduler.SchedulerAdapter; +import com.gyvex.ezafk.compatibility.scheduler.SchedulerAdapterFactory; +import com.gyvex.ezafk.config.ConfigManager; +import com.gyvex.ezafk.repository.StorageFactory; +import com.gyvex.ezafk.repository.StorageRepository; +import com.gyvex.ezafk.task.TaskManager; +import com.gyvex.ezafk.version.VersionAdapter; +import com.gyvex.ezafk.version.VersionAdapterFactory; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Logger; + +public class Registry { + private static Registry instance; + + private EzAfk plugin = null; + private Logger logger; + private ConfigManager configManager; + private FileConfiguration zonesConfig = null; + private TaskManager taskManager = null; + private Bootstrap bootstrap = null; + private StorageRepository storageRepository; + private SchedulerAdapter scheduler; + private VersionAdapter versionAdapter; + private final ArrayList registeredListeners = new ArrayList<>(); + + private Registry() { + } + + public static void init(EzAfk plugin) { + if (instance != null) return; + + instance = new Registry(); + instance.setPlugin(plugin); + instance.init(); + } + + public static void reset() { + instance = null; + } + + public void init() { + if (plugin == null) { + throw new IllegalStateException("Registry plugin not set"); + } + + this.configManager = new ConfigManager(plugin); + this.scheduler = SchedulerAdapterFactory.create(plugin); + this.versionAdapter = VersionAdapterFactory.create(plugin, this.logger); + this.bootstrap = new Bootstrap(plugin); + + try { + storageRepository = StorageFactory.create(); + if (storageRepository != null) storageRepository.init(); + } catch (Exception e) { + logger.warning("Failed to initialize storage repository: " + e.getMessage()); + } + } + + public static Registry get() { + if (instance == null) throw new IllegalStateException("Registry not initialized"); + return instance; + } + + public void setPlugin(EzAfk plugin) { + this.plugin = plugin; + this.logger = plugin.getLogger(); + } + + public EzAfk getPlugin() { + return plugin; + } + + public Logger getLogger() { + return logger; + } + + public ConfigManager getConfigManager() { + return configManager; + } + + public TaskManager getTaskManager() { + if (this.taskManager == null) this.taskManager = new TaskManager(); + return taskManager; + } + + public Bootstrap getBootstrap() { + return bootstrap; + } + + public com.gyvex.ezafk.repository.StorageRepository getStorageRepository() { + return storageRepository; + } + + public SchedulerAdapter getScheduler() { + return scheduler; + } + + public VersionAdapter getVersionAdapter() { + return versionAdapter; + } + + /** + * Reload the storage repository based on current configuration. + * This will create a new repository via StorageFactory, init it, + * replace the existing repository and shutdown the old one. + */ + public synchronized void reloadStorageRepository() { + com.gyvex.ezafk.repository.StorageRepository previous = this.storageRepository; + com.gyvex.ezafk.repository.StorageRepository next = null; + try { + next = com.gyvex.ezafk.repository.StorageFactory.create(); + if (next != null) { + try { + next.init(); + } catch (Exception e) { + logger.warning("Failed to initialize new storage repository: " + e.getMessage()); + } + } + this.storageRepository = next; + } catch (Exception e) { + logger.warning("Failed to reload storage repository: " + e.getMessage()); + } finally { + if (previous != null && previous != this.storageRepository) { + try { + previous.shutdown(); + } catch (Exception ignored) { + } + } + } + } + + public void registerListener(Listener listener) { + try { + plugin.getServer().getPluginManager().registerEvents(listener, plugin); + registeredListeners.add(listener); + } catch (org.bukkit.plugin.IllegalPluginAccessException ex) { + logger.warning("Failed to register listener " + listener.getClass().getSimpleName() + " during registry init: " + ex.getMessage()); + } + } + + public void unregisterAllListeners() { + for (Listener l : new ArrayList<>(registeredListeners)) { + HandlerList.unregisterAll(l); + } + registeredListeners.clear(); + } + + public void saveDefaultZonesConfig() { + if (this.zonesConfig == null) { + if (!plugin.getDataFolder().exists()) { + plugin.getDataFolder().mkdirs(); + } + } + plugin.saveResource("zones.yml", false); + } + + public void reloadZonesConfig() { + File zonesFile = new File(plugin.getDataFolder(), "zones.yml"); + if (!zonesFile.exists()) { + saveDefaultZonesConfig(); + } + + this.zonesConfig = YamlConfiguration.loadConfiguration(zonesFile); + } + + public FileConfiguration getZonesConfig() { + if (this.zonesConfig == null) { + reloadZonesConfig(); + } + return this.zonesConfig; + } + + public void saveZonesConfig() { + File zonesFile = new File(plugin.getDataFolder(), "zones.yml"); + try { + if (this.zonesConfig != null) { + this.zonesConfig.save(zonesFile); + } + } catch (IOException e) { + logger.warning("Unable to save zones.yml: " + e.getMessage()); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/command/AfkZoneCommand.java b/ezafk-core/src/main/java/com/gyvex/ezafk/command/AfkZoneCommand.java new file mode 100644 index 0000000..97fae67 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/command/AfkZoneCommand.java @@ -0,0 +1,299 @@ +package com.gyvex.ezafk.command; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.manager.AfkZoneManager; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.util.CommandUtil; +import org.bukkit.Bukkit; +import com.gyvex.ezafk.zone.ZoneCache; +import com.gyvex.ezafk.integration.WorldEditIntegration; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class AfkZoneCommand { + private final EzAfk plugin; + + public AfkZoneCommand(EzAfk plugin) { + this.plugin = plugin; + } + + public void handleAfkZone(CommandSender sender, String[] args) { + if (args.length < 2) { + MessageManager.sendMessage(sender, "command.usage", "&cUsage: /afkzone [name]"); + return; + } + + String action = args[1].toLowerCase(Locale.ROOT); + + switch (action) { + case "list": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.list", "command.usage", "&cYou don't have permission.")) return; + List rawList = Registry.get().getZonesConfig().getMapList("regions"); + if (rawList == null || rawList.isEmpty()) { + MessageManager.sendMessage(sender, "afkzone.list.empty", "&eNo AFK zones configured."); + return; + } + MessageManager.sendMessage(sender, "afkzone.list.header", "&6AFK Zones:"); + for (Object o : rawList) { + if (!(o instanceof java.util.Map)) continue; + @SuppressWarnings("unchecked") + java.util.Map map = (java.util.Map) o; + String name = String.valueOf(map.getOrDefault("name", "")); + String world = String.valueOf(map.getOrDefault("world", "")); + String coords = String.format("%s,%s,%s - %s,%s,%s", + map.getOrDefault("x1", ""), map.getOrDefault("y1", ""), map.getOrDefault("z1", ""), + map.getOrDefault("x2", ""), map.getOrDefault("y2", ""), map.getOrDefault("z2", "") + ); + MessageManager.sendMessage(sender, "afkzone.list.entry", "&e%name% &7(%world%): &f%coords%", Map.of("name", name, "world", world, "coords", coords)); + } + return; + case "players": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.list", "command.usage", "&cYou don't have permission.")) return; + java.util.List loadedZones = AfkZoneManager.getZones(); + if (loadedZones == null || loadedZones.isEmpty()) { + // Try to (re)load zones from configuration in case they were not loaded yet + try { + AfkZoneManager.load(plugin); + } catch (Exception ignored) {} + loadedZones = AfkZoneManager.getZones(); + } + if (loadedZones == null || loadedZones.isEmpty()) { + // Fallback: if still empty, enumerate configured regions so command shows something useful + List regionsList = Registry.get().getZonesConfig().getMapList("regions"); + if (regionsList == null || regionsList.isEmpty()) { + MessageManager.sendMessage(sender, "afkzone.list.empty", "&eNo AFK zones configured."); + return; + } + java.util.Map> playersByZoneCfg = new java.util.LinkedHashMap<>(); + for (Object o : regionsList) { + if (!(o instanceof java.util.Map)) continue; + @SuppressWarnings("unchecked") + java.util.Map map = (java.util.Map) o; + String zoneName = String.valueOf(map.getOrDefault("name", "")); + playersByZoneCfg.put(zoneName, new java.util.ArrayList<>()); + } + MessageManager.sendMessage(sender, "afkzone.players.header", "&6Players in AFK Zones:"); + for (java.util.Map.Entry> e : playersByZoneCfg.entrySet()) { + String zn = e.getKey(); + MessageManager.sendMessage(sender, "afkzone.players.entry", "&e%name%: &f%players%", java.util.Map.of("name", zn, "players", "")); + } + return; + } + java.util.Map> playersByZone = new java.util.LinkedHashMap<>(); + for (com.gyvex.ezafk.zone.Zone z : loadedZones) { + String zoneName = z.name == null || z.name.isEmpty() ? "" : z.name; + playersByZone.put(zoneName, new java.util.ArrayList<>()); + } + for (org.bukkit.entity.Player online : org.bukkit.Bukkit.getOnlinePlayers()) { + com.gyvex.ezafk.zone.Zone z = AfkZoneManager.getZoneForPlayer(online); + if (z != null) { + String zn = z.name == null || z.name.isEmpty() ? "" : z.name; + playersByZone.computeIfAbsent(zn, k -> new java.util.ArrayList<>()).add(online.getName()); + } + } + MessageManager.sendMessage(sender, "afkzone.players.header", "&6Players in AFK Zones:"); + for (java.util.Map.Entry> e : playersByZone.entrySet()) { + String zn = e.getKey(); + java.util.List listPlayers = e.getValue(); + String playersStr = listPlayers.isEmpty() ? "" : String.join(", ", listPlayers); + MessageManager.sendMessage(sender, "afkzone.players.entry", "&e%name%: &f%players%", java.util.Map.of("name", zn, "players", playersStr)); + } + return; + case "add": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "afkzone.add.player-only", "&cOnly players can create AFK zones."); + return; + } + if (args.length < 3) { + MessageManager.sendMessage(sender, "command.usage", "&cUsage: /afkzone add "); + return; + } + String name = args[2]; + Player p = (Player) sender; + java.util.Map region = new java.util.HashMap<>(); + region.put("name", name); + + // Attempt to use WorldEdit region selection if available, else fall back to stored pos1/pos2, else require explicit coordinates + org.bukkit.Location minLoc = null; + org.bukkit.Location maxLoc = null; + boolean usedSelection = false; + org.bukkit.Location[] sel = WorldEditIntegration.getSelectionLocations(plugin, p); + if (sel != null && sel.length == 2) { + minLoc = sel[0]; + maxLoc = sel[1]; + usedSelection = true; + } + if (!usedSelection) { + // Check for stored pos1/pos2 + java.util.UUID pu = p.getUniqueId(); + org.bukkit.Location p1 = ZoneCache.zonePos1.get(pu); + org.bukkit.Location p2 = ZoneCache.zonePos2.get(pu); + if (p1 != null && p2 != null) { + minLoc = new org.bukkit.Location(p1.getWorld(), Math.min(p1.getX(), p2.getX()), Math.min(p1.getY(), p2.getY()), Math.min(p1.getZ(), p2.getZ())); + maxLoc = new org.bukkit.Location(p1.getWorld(), Math.max(p1.getX(), p2.getX()), Math.max(p1.getY(), p2.getY()), Math.max(p1.getZ(), p2.getZ())); + } else if (args.length >= 9) { + try { + double ax1 = Double.parseDouble(args[3]); + double ay1 = Double.parseDouble(args[4]); + double az1 = Double.parseDouble(args[5]); + double ax2 = Double.parseDouble(args[6]); + double ay2 = Double.parseDouble(args[7]); + double az2 = Double.parseDouble(args[8]); + minLoc = new org.bukkit.Location(p.getWorld(), Math.min(ax1, ax2), Math.min(ay1, ay2), Math.min(az1, az2)); + maxLoc = new org.bukkit.Location(p.getWorld(), Math.max(ax1, ax2), Math.max(ay1, ay2), Math.max(az1, az2)); + } catch (NumberFormatException ex) { + MessageManager.sendMessage(sender, "afkzone.add.coords-invalid", "&cInvalid coordinates provided. Use numbers."); + return; + } + } else { + MessageManager.sendMessage(sender, "afkzone.add.selection-missing", "&cNo WorldEdit selection found - please select a region or provide explicit coordinates, or set pos1/pos2."); + return; + } + } + + region.put("world", minLoc.getWorld().getName()); + region.put("x1", minLoc.getX()); + region.put("y1", minLoc.getY()); + region.put("z1", minLoc.getZ()); + region.put("x2", maxLoc.getX()); + region.put("y2", maxLoc.getY()); + region.put("z2", maxLoc.getZ()); + + List rawRegions = Registry.get().getZonesConfig().getMapList("regions"); + java.util.List> regions; + if (rawRegions == null) { + regions = new java.util.ArrayList<>(); + } else { + regions = new java.util.ArrayList<>(); + for (Object o : rawRegions) { + if (!(o instanceof java.util.Map)) continue; + @SuppressWarnings("unchecked") + java.util.Map m = (java.util.Map) o; + regions.add(m); + } + } + regions.add(region); + Registry.get().getZonesConfig().set("regions", regions); + Registry.get().getZonesConfig().set("enabled", true); + Registry.get().saveZonesConfig(); + Registry.get().reloadZonesConfig(); + AfkZoneManager.load(plugin); + // clear stored pos1/pos2 for this player if present + ZoneCache.zonePos1.remove(p.getUniqueId()); + ZoneCache.zonePos2.remove(p.getUniqueId()); + MessageManager.sendMessage(sender, "afkzone.add.success", "&aAFK zone %name% added at your location.", Map.of("name", name)); + return; + case "remove": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + if (args.length < 3) { + MessageManager.sendMessage(sender, "command.usage", "&cUsage: /afkzone remove "); + return; + } + String removeName = args[2]; + List rawCurrent = Registry.get().getZonesConfig().getMapList("regions"); + java.util.List> current = new java.util.ArrayList<>(); + if (rawCurrent != null) { + for (Object o : rawCurrent) { + if (!(o instanceof java.util.Map)) continue; + @SuppressWarnings("unchecked") + java.util.Map m = (java.util.Map) o; + current.add(m); + } + } + boolean removed = false; + java.util.Iterator> it = current.iterator(); + while (it.hasNext()) { + java.util.Map m = it.next(); + Object n = m.get("name"); + if (n != null && removeName.equalsIgnoreCase(n.toString())) { + it.remove(); + removed = true; + break; + } + } + if (!removed) { + MessageManager.sendMessage(sender, "afkzone.remove.notfound", "&cAFK zone '%name%' not found.", Map.of("name", removeName)); + return; + } + Registry.get().getZonesConfig().set("regions", current); + Registry.get().saveZonesConfig(); + Registry.get().reloadZonesConfig(); + AfkZoneManager.load(Registry.get().getPlugin()); + MessageManager.sendMessage(sender, "afkzone.remove.success", "&aAFK zone '%name%' removed.", Map.of("name", removeName)); + return; + case "pos1": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "afkzone.add.player-only", "&cOnly players can set positions."); + return; + } + Player pp1 = (Player) sender; + ZoneCache.zonePos1.put(pp1.getUniqueId(), pp1.getLocation()); + ZoneCache.zonePos1Time.put(pp1.getUniqueId(), System.currentTimeMillis()); + MessageManager.sendMessage(sender, "afkzone.add.pos1.set", "&aPosition 1 set."); + return; + case "pos2": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "afkzone.add.player-only", "&cOnly players can set positions."); + return; + } + Player pp2 = (Player) sender; + ZoneCache.zonePos2.put(pp2.getUniqueId(), pp2.getLocation()); + ZoneCache.zonePos2Time.put(pp2.getUniqueId(), System.currentTimeMillis()); + MessageManager.sendMessage(sender, "afkzone.add.pos2.set", "&aPosition 2 set."); + return; + case "clearpos": + // /afk zone clearpos [player] + if (args.length >= 3) { + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + Player target = Bukkit.getPlayer(args[2]); + if (target == null) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + ZoneCache.clearPositions(target.getUniqueId()); + MessageManager.sendMessage(sender, "afkzone.add.pos-cleared-target", "&aCleared stored positions for %player%.", Map.of("player", target.getName())); + return; + } else { + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "afkzone.add.player-only", "&cOnly players can clear their own stored positions."); + return; + } + Player self = (Player) sender; + ZoneCache.clearPositions(self.getUniqueId()); + MessageManager.sendMessage(sender, "afkzone.add.pos-cleared-self", "&aCleared your stored positions."); + return; + } + case "reset": + if (!CommandUtil.checkPermission(sender, "ezafk.zone.manage", "command.usage", "&cYou don't have permission.")) return; + if (args.length < 3) { + MessageManager.sendMessage(sender, "command.usage", "&cUsage: /afkzone reset [zone]"); + return; + } + Player target = Bukkit.getPlayer(args[2]); + if (target == null) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + if (args.length >= 4) { + String zoneName = args[3]; + com.gyvex.ezafk.manager.AfkZoneRewardManager.resetPlayerCountForZone(target.getUniqueId(), zoneName); + MessageManager.sendMessage(sender, "afkzone.reset.zone.success", "&aReset reward counts for %player% in zone %zone%.", Map.of("player", target.getName(), "zone", zoneName)); + } else { + com.gyvex.ezafk.manager.AfkZoneRewardManager.resetPlayerCounts(target.getUniqueId()); + MessageManager.sendMessage(sender, "afkzone.reset.success", "&aReset reward counts for %player%.", Map.of("player", target.getName())); + } + return; + default: + MessageManager.sendMessage(sender, "command.usage", "&cUsage: /afkzone [name]"); + return; + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkCommand.java b/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkCommand.java new file mode 100644 index 0000000..21bff7f --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkCommand.java @@ -0,0 +1,484 @@ +package com.gyvex.ezafk.command; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.listener.AfkPlayerActionsGUI; +import com.gyvex.ezafk.listener.AfkPlayerOverviewGUI; +import com.gyvex.ezafk.integration.TabIntegration; +import com.gyvex.ezafk.manager.AfkTimeManager; +import com.gyvex.ezafk.manager.BypassListManager; +import com.gyvex.ezafk.manager.IntegrationManager; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.state.AfkReason; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.state.AfkStatusDetails; +import com.gyvex.ezafk.state.LastActiveState; +import com.gyvex.ezafk.state.ToggleResult; +import com.gyvex.ezafk.util.CommandUtil; +import com.gyvex.ezafk.util.DurationFormatter; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class EzAfkCommand implements CommandExecutor { + private static final int TOP_LEADERBOARD_SIZE = 10; + private final EzAfk plugin; + private final AfkZoneCommand afkZoneCommand; + + // Zone position storage and helpers moved to AfkZoneCommand + + public EzAfkCommand(EzAfk plugin) { + this.plugin = plugin; + this.afkZoneCommand = new AfkZoneCommand(plugin); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + String lowerLabel = label.toLowerCase(Locale.ROOT); + if ("afktime".equals(lowerLabel)) { + handleTime(sender, args, 0); + return true; + } + + if ("afktop".equals(lowerLabel)) { + handleTop(sender); + return true; + } + + if ("afkzone".equals(lowerLabel)) { + // /afkzone add|list|remove|pos1|pos2|reset ... -> map to /afk zone ... + String[] newArgs = new String[args.length + 1]; + newArgs[0] = "zone"; + System.arraycopy(args, 0, newArgs, 1, args.length); + afkZoneCommand.handleAfkZone(sender, newArgs); + return true; + } + + if (args.length == 0) { + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "command.toggle.self.only-players", "&cOnly players can toggle their own AFK state."); + return true; + } + + Player player = (Player) sender; + ToggleResult result = AfkState.toggle(this.plugin, player); + + if (result == ToggleResult.FAILED) { + MessageManager.sendMessage(player, "command.toggle.self.failed", "&cYour AFK status could not be updated."); + } + return true; + } + + String subCommand = args[0].toLowerCase(Locale.ROOT); + + switch (subCommand) { + case "reload": + handleReload(sender); + return true; + case "gui": + handleGui(sender); + return true; + case "toggle": + handleToggle(sender, args); + return true; + case "bypass": + handleBypass(sender, args); + return true; + case "info": + handleInfo(sender, args); + return true; + case "time": + handleTime(sender, args, 1); + return true; + case "top": + handleTop(sender); + return true; + case "zone": + afkZoneCommand.handleAfkZone(sender, args); + return true; + default: + MessageManager.sendMessage(sender, "command.usage", getUsageFallback()); + return true; + } + } + + private void handleReload(CommandSender sender) { + if (!sender.hasPermission("ezafk.reload")) { + MessageManager.sendMessage(sender, "command.reload.no-permission", "&cYou don't have permission to use this command."); + return; + } + + Registry.get().getConfigManager().loadConfig(); + IntegrationManager.unload(); + IntegrationManager.load(); + TabIntegration tabIntegration = (TabIntegration) IntegrationManager.getIntegration("tab"); + if (tabIntegration != null) { + tabIntegration.reloadFromConfig(); + tabIntegration.update(); + } + // Reload storage repository according to new configuration + try { + Registry.get().reloadStorageRepository(); + } catch (Exception ignored) {} + // Reload zones configuration (zones.yml) and refresh in-memory zones + try { + Registry.get().reloadZonesConfig(); + com.gyvex.ezafk.manager.AfkZoneManager.load(Registry.get().getPlugin()); + } catch (Exception ignored) {} + AfkPlayerActionsGUI.reloadConfiguredActions(); + BypassListManager.load(plugin); + MessageManager.sendMessage(sender, "command.reload.success", "&aConfig reloaded."); + } + + private void handleGui(CommandSender sender) { + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "command.gui.player-only", "&cOnly players can use this command."); + return; + } + Player player = (Player) sender; + if (!player.hasPermission("ezafk.gui") && !player.isOp()) { + MessageManager.sendMessage(sender, "command.gui.no-permission", "&cYou don't have permission to use this command."); + return; + } + AfkPlayerOverviewGUI gui = new AfkPlayerOverviewGUI(); + gui.openGUI(player, 1); + } + + private void handleToggle(CommandSender sender, String[] args) { + if (!CommandUtil.checkPermission(sender, "ezafk.toggle", "command.toggle.no-permission", "&cYou don't have permission to use this command.")) return; + + if (args.length < 2) { + MessageManager.sendMessage(sender, "command.toggle.usage", "&cUsage: /afk toggle "); + return; + } + + Player target = Bukkit.getPlayer(args[1]); + if (target == null) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + + boolean initiatedByTarget = sender instanceof Player && sender.equals(target); + AfkReason reason = initiatedByTarget ? AfkReason.MANUAL : AfkReason.COMMAND_FORCED; + // Sanitize sender name for detail + String senderName = sender.getName().replaceAll("[^a-zA-Z0-9_\\-]", ""); + String detail = initiatedByTarget ? null : "Triggered by " + senderName; + + ToggleResult result = AfkState.toggle(this.plugin, target, initiatedByTarget, reason, detail); + + switch (result) { + case NOW_AFK: + MessageManager.sendMessage(sender, "command.toggle.now-afk", "&a%player% is now AFK.", Map.of("player", target.getName())); + if (initiatedByTarget) { + MessageManager.sendMessage(target, "command.toggle.self-now-afk", "&aYou are now AFK."); + } + break; + case NO_LONGER_AFK: + MessageManager.sendMessage(sender, "command.toggle.no-longer-afk", "&a%player% is no longer AFK.", Map.of("player", target.getName())); + if (initiatedByTarget) { + MessageManager.sendMessage(target, "command.toggle.self-no-longer-afk", "&aYou are no longer AFK."); + } + break; + case FAILED: + MessageManager.sendMessage(sender, "command.toggle.failed", "&cFailed to toggle AFK status for %player%.", Map.of("player", target.getName())); + if (initiatedByTarget) { + MessageManager.sendMessage(target, "command.toggle.self.failed", "&cYour AFK status could not be updated."); + } + break; + } + } + + private void handleBypass(CommandSender sender, String[] args) { + if (!sender.hasPermission("ezafk.bypass.manage")) { + MessageManager.sendMessage(sender, "command.bypass.no-permission", "&cYou don't have permission to change AFK bypass states."); + return; + } + + if (args.length < 2) { + MessageManager.sendMessage(sender, "command.usage", getUsageFallback()); + return; + } + + String subOrTarget = args[1].toLowerCase(Locale.ROOT); + if (subOrTarget.equals("whitelist") || subOrTarget.equals("blacklist")) { + handleBypassList(sender, args, subOrTarget.equals("whitelist")); + return; + } + + // Legacy: /afk bypass — session toggle + Player target = Bukkit.getPlayer(args[1]); + if (target == null) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + + boolean bypassEnabled = AfkState.toggleBypass(target.getUniqueId()); + if (bypassEnabled) { + MessageManager.sendMessage(sender, "command.bypass.enabled", "&a%target% will now bypass AFK detection.", Map.of("target", target.getName())); + if (sender != target) { + MessageManager.sendMessage(target, "command.bypass.enabled-target", "&aYou will now bypass AFK detection."); + } + } else { + MessageManager.sendMessage(sender, "command.bypass.disabled", "&a%target% will no longer bypass AFK detection.", Map.of("target", target.getName())); + if (sender != target) { + MessageManager.sendMessage(target, "command.bypass.disabled-target", "&cYou will no longer bypass AFK detection."); + } + } + } + + private void handleBypassList(CommandSender sender, String[] args, boolean isWhitelist) { + String listName = isWhitelist ? "whitelist" : "blacklist"; + + if (args.length < 3) { + MessageManager.sendMessage(sender, "command.usage", + "&cUsage: /afk bypass " + listName + " [player]"); + return; + } + + String action = args[2].toLowerCase(Locale.ROOT); + + if (action.equals("list")) { + java.util.Set list = isWhitelist ? BypassListManager.getWhitelist() : BypassListManager.getBlacklist(); + if (list.isEmpty()) { + MessageManager.sendMessage(sender, "command.bypass." + listName + ".empty", + "&eThe bypass " + listName + " is empty."); + return; + } + MessageManager.sendMessage(sender, "command.bypass." + listName + ".header", + "&6Bypass " + listName + ":"); + for (UUID id : list) { + String name = Bukkit.getOfflinePlayer(id).getName(); + String display = name != null ? name : id.toString(); + MessageManager.sendMessage(sender, "command.bypass." + listName + ".entry", + "&e- &f%player%", Map.of("player", display)); + } + return; + } + + if (args.length < 4) { + MessageManager.sendMessage(sender, "command.usage", + "&cUsage: /afk bypass " + listName + " "); + return; + } + + OfflinePlayer target = CommandUtil.findPlayer(args[3]); + if (target.getName() == null && !target.hasPlayedBefore() && !target.isOnline()) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + String targetName = target.getName() != null ? target.getName() : target.getUniqueId().toString(); + UUID targetId = target.getUniqueId(); + + switch (action) { + case "add" -> { + if (isWhitelist) { + if (BypassListManager.addToWhitelist(targetId)) { + MessageManager.sendMessage(sender, "command.bypass.whitelist.added", + "&a%player% has been added to the bypass whitelist.", Map.of("player", targetName)); + Player online = Bukkit.getPlayer(targetId); + if (online != null && !sender.equals(online)) { + MessageManager.sendMessage(online, "command.bypass.whitelist.added-target", + "&aYou have been added to the AFK bypass whitelist."); + } + } else { + MessageManager.sendMessage(sender, "command.bypass.whitelist.already", + "&e%player% is already on the bypass whitelist.", Map.of("player", targetName)); + } + } else { + if (BypassListManager.addToBlacklist(targetId)) { + MessageManager.sendMessage(sender, "command.bypass.blacklist.added", + "&a%player% has been added to the bypass blacklist.", Map.of("player", targetName)); + Player online = Bukkit.getPlayer(targetId); + if (online != null && !sender.equals(online)) { + MessageManager.sendMessage(online, "command.bypass.blacklist.added-target", + "&cYou have been added to the AFK bypass blacklist and will now be subject to AFK detection."); + } + } else { + MessageManager.sendMessage(sender, "command.bypass.blacklist.already", + "&e%player% is already on the bypass blacklist.", Map.of("player", targetName)); + } + } + } + case "remove" -> { + if (isWhitelist) { + if (BypassListManager.removeFromWhitelist(targetId)) { + MessageManager.sendMessage(sender, "command.bypass.whitelist.removed", + "&a%player% has been removed from the bypass whitelist.", Map.of("player", targetName)); + Player online = Bukkit.getPlayer(targetId); + if (online != null && !sender.equals(online)) { + MessageManager.sendMessage(online, "command.bypass.whitelist.removed-target", + "&cYou have been removed from the AFK bypass whitelist."); + } + } else { + MessageManager.sendMessage(sender, "command.bypass.whitelist.not-found", + "&e%player% is not on the bypass whitelist.", Map.of("player", targetName)); + } + } else { + if (BypassListManager.removeFromBlacklist(targetId)) { + MessageManager.sendMessage(sender, "command.bypass.blacklist.removed", + "&a%player% has been removed from the bypass blacklist.", Map.of("player", targetName)); + Player online = Bukkit.getPlayer(targetId); + if (online != null && !sender.equals(online)) { + MessageManager.sendMessage(online, "command.bypass.blacklist.removed-target", + "&aYou have been removed from the AFK bypass blacklist."); + } + } else { + MessageManager.sendMessage(sender, "command.bypass.blacklist.not-found", + "&e%player% is not on the bypass blacklist.", Map.of("player", targetName)); + } + } + } + default -> MessageManager.sendMessage(sender, "command.usage", + "&cUsage: /afk bypass " + listName + " [player]"); + } + } + + private void handleInfo(CommandSender sender, String[] args) { + if (!CommandUtil.checkPermission(sender, "ezafk.info", "command.info.no-permission", "&cYou don't have permission to use this command.")) return; + + if (args.length < 2) { + MessageManager.sendMessage(sender, "command.info.usage", "&cUsage: /afk info "); + return; + } + + String nameArg = args[1]; + OfflinePlayer target = CommandUtil.findPlayer(nameArg); + if (target.getName() == null && !target.hasPlayedBefore() && !target.isOnline()) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + + UUID targetId = target.getUniqueId(); + if (!AfkState.isAfk(targetId)) { + MessageManager.sendMessage(sender, "command.info.not-afk", "&a%player% is not currently AFK.", Map.of("player", target.getName() != null ? target.getName() : targetId.toString())); + return; + } + + String targetName = target.getName() != null ? target.getName() : targetId.toString(); + AfkStatusDetails details = AfkState.getAfkStatusDetails(targetId); + String reasonText = details != null ? details.getReasonDisplayName() : AfkReason.OTHER.getDisplayName(); + long afkSeconds = AfkState.getSecondsSinceAfk(targetId); + long lastActivity = LastActiveState.getSecondsSinceLastActive(targetId); + + MessageManager.sendMessage(sender, "command.info.header", "&6AFK report for &e%player%&6:", Map.of("player", targetName)); + MessageManager.sendMessage(sender, "command.info.reason", "&7Reason: &f%reason%", Map.of("player", targetName, "reason", reasonText)); + + if (details != null && details.hasDetail()) { + MessageManager.sendMessage(sender, "command.info.detail", "&7Detail: &f%detail%", Map.of("player", targetName, "detail", details.getDetail())); + } else { + MessageManager.sendMessage(sender, "command.info.no-detail", "&7No additional details.", Map.of("player", targetName)); + } + + MessageManager.sendMessage(sender, "command.info.duration", "&7AFK for: &f%duration%", Map.of("player", targetName, "duration", DurationFormatter.formatDuration(afkSeconds))); + MessageManager.sendMessage(sender, "command.info.last-activity", "&7Last activity: &f%last% ago", Map.of("player", targetName, "last", DurationFormatter.formatDuration(lastActivity))); + } + + private void handleTime(CommandSender sender, String[] args, int startIndex) { + int remainingArguments = Math.max(0, args.length - startIndex); + + // Support: /afk time reset + if (remainingArguments >= 2 && args[startIndex].equalsIgnoreCase("reset")) { + if (!sender.hasPermission("ezafk.time.reset")) { + MessageManager.sendMessage(sender, "command.time.reset.no-permission", "&cYou don't have permission to reset player AFK time."); + return; + } + + String targetName = args[startIndex + 1]; + OfflinePlayer target = Bukkit.getOfflinePlayer(targetName); + if (target == null || (target.getName() == null && !target.hasPlayedBefore() && !target.isOnline())) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + + boolean ok = AfkTimeManager.resetPlayer(target.getUniqueId()); + if (ok) { + MessageManager.sendMessage(sender, "command.time.reset.success", "&aReset AFK time for %player%.", Map.of("player", target.getName() != null ? target.getName() : target.getUniqueId().toString())); + } else { + MessageManager.sendMessage(sender, "command.time.reset.failed", "&cFailed to reset AFK time for %player%.", Map.of("player", target.getName() != null ? target.getName() : target.getUniqueId().toString())); + } + return; + } + + if (remainingArguments > 1) { + MessageManager.sendMessage(sender, "command.usage", getUsageFallback()); + return; + } + + if (remainingArguments == 0) { + if (!(sender instanceof Player)) { + MessageManager.sendMessage(sender, "command.time.players-only", "&cOnly players can view their own AFK time."); + return; + } + + if (!sender.hasPermission("ezafk.time")) { + MessageManager.sendMessage(sender, "command.time.no-permission", "&cYou don't have permission to view your AFK time."); + return; + } + + Player player = (Player) sender; + long totalSeconds = AfkState.getTotalAfkSeconds(player.getUniqueId()); + MessageManager.sendMessage(sender, "command.time.self", "&aYou have been AFK for %duration% in total.", + Map.of("duration", DurationFormatter.formatDuration(totalSeconds))); + return; + } + + if (!sender.hasPermission("ezafk.time.others")) { + MessageManager.sendMessage(sender, "command.time.others-no-permission", "&cYou don't have permission to view other players' AFK time."); + return; + } + + String targetNameArg = args[startIndex]; + Player onlineTarget = Bukkit.getPlayer(targetNameArg); + OfflinePlayer target; + if (onlineTarget != null) { + target = onlineTarget; + } else { + // Fallback: unavoidable for legacy, but safe + target = Bukkit.getOfflinePlayer(targetNameArg); + } + if (target == null || (target.getName() == null && !target.hasPlayedBefore() && !target.isOnline())) { + MessageManager.sendMessage(sender, "command.player-not-found", "&cPlayer not found."); + return; + } + + UUID targetId = target.getUniqueId(); + long totalSeconds = AfkState.getTotalAfkSeconds(targetId); + String targetName = target.getName() != null ? target.getName() : targetId.toString(); + MessageManager.sendMessage(sender, "command.time.other", "&a%player% has been AFK for %duration% in total.", + Map.of("player", targetName, "duration", DurationFormatter.formatDuration(totalSeconds))); + } + + private void handleTop(CommandSender sender) { + if (!CommandUtil.checkPermission(sender, "ezafk.top", "command.top.no-permission", "&cYou don't have permission to use this command.")) return; + + List> topEntries = AfkTimeManager.getTopPlayers(TOP_LEADERBOARD_SIZE); + if (topEntries.isEmpty()) { + MessageManager.sendMessage(sender, "command.top.empty", "&cNo AFK data available."); + return; + } + + MessageManager.sendMessage(sender, "command.top.header", "&6Top AFK players:"); + + int position = 1; + for (Map.Entry entry : topEntries) { + String playerName = Bukkit.getOfflinePlayer(entry.getKey()).getName(); + String name = playerName != null ? playerName : entry.getKey().toString(); + String duration = DurationFormatter.formatDuration(entry.getValue()); + MessageManager.sendMessage(sender, "command.top.entry", "&e#%pos% &f%player%: &b%duration%", Map.of("pos", String.valueOf(position), "player", name, "duration", duration)); + position++; + } + } + + private String getUsageFallback() { + return "&cUsage: /afk [reload|gui|toggle |bypass [player]|bypass |info |time [player]|top|zone ]"; + } + +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkTabCompleter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkTabCompleter.java new file mode 100644 index 0000000..a0883f3 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/command/EzAfkTabCompleter.java @@ -0,0 +1,132 @@ +package com.gyvex.ezafk.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; + +import com.gyvex.ezafk.bootstrap.Registry; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public class EzAfkTabCompleter implements TabCompleter { + private static final List MAIN_COMMANDS = Arrays.asList( + "reload", "gui", "toggle", "bypass", "info", "time", "top", "zone" + ); + + private static final List ZONE_COMMANDS = Arrays.asList( + "list", "add", "remove", "pos1", "pos2", "reset" + ); + + private static final List BYPASS_LIST_TYPES = Arrays.asList("whitelist", "blacklist"); + private static final List BYPASS_LIST_ACTIONS = Arrays.asList("add", "remove", "list"); + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + String prefix = args[0].toLowerCase(Locale.ROOT); + String labelLower = alias.toLowerCase(Locale.ROOT); + if ("afkzone".equals(labelLower)) { + return ZONE_COMMANDS.stream().filter(cmd -> cmd.startsWith(prefix)).collect(Collectors.toList()); + } + return MAIN_COMMANDS.stream() + .filter(cmd -> cmd.startsWith(prefix)) + .collect(Collectors.toList()); + } + if (args.length == 2) { + String sub = args[0].toLowerCase(Locale.ROOT); + String prefix = args[1].toLowerCase(Locale.ROOT); + // If user used /afkzone + if ("afkzone".equals(alias.toLowerCase(Locale.ROOT))) { + sub = args.length > 0 ? args[0].toLowerCase(Locale.ROOT) : ""; + } + + if (sub.equals("bypass")) { + // Suggest whitelist/blacklist types AND online player names (for legacy /afk bypass ) + List suggestions = new java.util.ArrayList<>(); + BYPASS_LIST_TYPES.stream() + .filter(t -> t.startsWith(prefix)) + .forEach(suggestions::add); + Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix)) + .sorted(String.CASE_INSENSITIVE_ORDER) + .forEach(suggestions::add); + return suggestions; + } + + if (sub.equals("toggle") || sub.equals("info") || (sub.equals("time") && sender.hasPermission("ezafk.time.others"))) { + List playerNames = Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix)) + .collect(Collectors.toList()); + Collections.sort(playerNames, String.CASE_INSENSITIVE_ORDER); + return playerNames; + } + + if (sub.equals("reset")) { + List playerNames = Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix)) + .collect(Collectors.toList()); + Collections.sort(playerNames, String.CASE_INSENSITIVE_ORDER); + return playerNames; + } + + if (sub.equals("remove")) { + // suggest zone names from zones.yml + try { + java.util.List raw = Registry.get().getZonesConfig().getMapList("regions"); + if (raw != null) { + List zones = new java.util.ArrayList<>(); + for (Object o : raw) { + if (!(o instanceof java.util.Map)) continue; + @SuppressWarnings("unchecked") + java.util.Map m = (java.util.Map) o; + String name = String.valueOf(m.getOrDefault("name", "")); + if (!name.isEmpty() && name.toLowerCase(Locale.ROOT).startsWith(prefix)) { + zones.add(name); + } + } + java.util.Collections.sort(zones, String.CASE_INSENSITIVE_ORDER); + return zones; + } + } catch (Exception ignored) {} + } + } + // /afk bypass + if (args.length == 3) { + String sub = args[0].toLowerCase(Locale.ROOT); + String listType = args[1].toLowerCase(Locale.ROOT); + String prefix = args[2].toLowerCase(Locale.ROOT); + if (sub.equals("bypass") && (listType.equals("whitelist") || listType.equals("blacklist"))) { + return BYPASS_LIST_ACTIONS.stream() + .filter(a -> a.startsWith(prefix)) + .collect(Collectors.toList()); + } + } + // /afk bypass + if (args.length == 4) { + String sub = args[0].toLowerCase(Locale.ROOT); + String listType = args[1].toLowerCase(Locale.ROOT); + String action = args[2].toLowerCase(Locale.ROOT); + String prefix = args[3].toLowerCase(Locale.ROOT); + if (sub.equals("bypass") + && (listType.equals("whitelist") || listType.equals("blacklist")) + && (action.equals("add") || action.equals("remove"))) { + List playerNames = Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(prefix)) + .collect(Collectors.toList()); + Collections.sort(playerNames, String.CASE_INSENSITIVE_ORDER); + return playerNames; + } + } + return Collections.emptyList(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java new file mode 100644 index 0000000..bd988d1 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java @@ -0,0 +1,115 @@ +package com.gyvex.ezafk.compatibility; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.ItemMeta; + +import com.gyvex.ezafk.util.PlaceholderUtil; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * Utility class for handling item lore and display name formatting with MiniMessage support. + * + *

Adventure/MiniMessage is bundled (shaded) into the plugin JAR, so it is always available + * regardless of whether the server is Paper or plain Spigot. All formatting is resolved to + * legacy §-prefixed color strings, which are then applied via the universal + * {@link ItemMeta#setLore}/{@link ItemMeta#setDisplayName} API. This avoids any class-type + * mismatch between the bundled Adventure classes and the server's own Adventure API.

+ */ +public final class LoreUtil { + + private LoreUtil() { + // Utility class + } + + /** + * Sets the lore on the given ItemMeta, resolving placeholders and applying MiniMessage + * (or legacy {@code &} color-code) formatting. + */ + public static void setLore(ItemMeta meta, List rawLore, Player player, Logger logger) { + if (rawLore == null || rawLore.isEmpty()) { + return; + } + + List resolvedLore = new ArrayList<>(); + for (String line : rawLore) { + String resolvedLine = PlaceholderUtil.resolvePlaceholders(player, line, logger); + resolvedLore.add(toDisplayString(resolvedLine, logger)); + } + meta.setLore(resolvedLore); + } + + /** + * Sets the display name on the given ItemMeta, resolving placeholders and applying + * MiniMessage (or legacy {@code &} color-code) formatting. + */ + public static void setDisplayName(ItemMeta meta, String rawDisplayName, Player player, Logger logger) { + if (rawDisplayName == null || rawDisplayName.isBlank()) return; + + String resolvedName = PlaceholderUtil.resolvePlaceholders(player, rawDisplayName, logger); + meta.setDisplayName(toDisplayString(resolvedName, logger)); + } + + /** + * Validates a MiniMessage string by attempting to deserialize it. + */ + public static boolean validateMiniMessage(String miniMessageString, String context, Logger logger) { + if (miniMessageString == null || miniMessageString.isBlank()) return true; + try { + Object comp = deserializeToComponent(miniMessageString); + return comp != null; + } catch (Exception e) { + if (logger != null) logger.warning("Invalid MiniMessage in " + context + ": '" + miniMessageString + "' - " + e.getMessage()); + return false; + } + } + + /** + * Converts a raw message string to a legacy §-color string suitable for + * {@link ItemMeta#setDisplayName} / {@link ItemMeta#setLore} and chat messages. + * + *

When the bundled MiniMessage library is available (always, since it is shaded into the + * JAR), MiniMessage tags such as {@code } are parsed and the result is serialized back + * to a legacy color string. Otherwise, {@code &}-color codes are translated directly.

+ */ + public static String toDisplayString(String text, Logger logger) { + try { + Object component = deserializeToComponent(text); + if (component != null) { + // Serialize MiniMessage component to §-prefixed legacy string, then + // also translate any &-codes that MiniMessage left as plain text + // (e.g. "&cRed" is not MiniMessage syntax, so it survives as-is). + return PlaceholderUtil.colorize(serializeComponentToLegacy(component)); + } + } catch (Exception e) { + if (logger != null) logger.fine("MiniMessage parse failed, falling back to legacy colors: " + e.getMessage()); + } + return PlaceholderUtil.colorize(text); + } + + private static Object deserializeToComponent(String miniMessage) { + try { + Class miniCls = Class.forName("net.kyori.adventure.text.minimessage.MiniMessage"); + Method miniFactory = miniCls.getMethod("miniMessage"); + Object mini = miniFactory.invoke(null); + // Adventure 4.26+ removed the single-arg deserialize(String); use the + // raw ComponentSerializer.deserialize(Object) bridge method instead. + Method deserialize = miniCls.getMethod("deserialize", Object.class); + return deserialize.invoke(mini, miniMessage); + } catch (Throwable t) { + return null; + } + } + + private static String serializeComponentToLegacy(Object component) throws Exception { + if (component == null) return ""; + Class legacyCls = Class.forName("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer"); + Method legacySection = legacyCls.getMethod("legacySection"); + Object serializer = legacySection.invoke(null); + Method serialize = legacyCls.getMethod("serialize", Class.forName("net.kyori.adventure.text.Component")); + return (String) serialize.invoke(serializer, component); + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/ParticleCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/ParticleCompat.java new file mode 100644 index 0000000..8d41e3b --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/ParticleCompat.java @@ -0,0 +1,49 @@ +package com.gyvex.ezafk.compatibility; + +import org.bukkit.Particle; + +public class ParticleCompat { + public static Particle getHappyVillager() { + try { + return Particle.valueOf("HAPPY_VILLAGER"); + } catch (IllegalArgumentException e) { + try { + return Enum.valueOf(Particle.class, "VILLAGER_HAPPY"); + } catch (IllegalArgumentException e2) { + return null; + } + } + } + + public static Particle getBlock() { + try { + return Particle.valueOf("BLOCK"); + } catch (IllegalArgumentException e) { + try { + return Enum.valueOf(Particle.class, "BLOCK_CRACK"); + } catch (IllegalArgumentException e2) { + return null; + } + } + } + + public static Particle getCherryLeaves() { + try { + return Particle.valueOf("CHERRY_LEAVES"); + } catch (IllegalArgumentException e) { + return null; // not available in older versions + } + } + + public static Particle getSmoke() { + try { + return Particle.valueOf("SMOKE"); + } catch (IllegalArgumentException e) { + try { + return Enum.valueOf(Particle.class, "SMOKE_NORMAL"); + } catch (IllegalArgumentException e2) { + return null; + } + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/inventory/InventoryCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/inventory/InventoryCompat.java new file mode 100644 index 0000000..251c840 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/inventory/InventoryCompat.java @@ -0,0 +1,40 @@ +package com.gyvex.ezafk.compatibility.inventory; + +import org.bukkit.Bukkit; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class InventoryCompat { + + private InventoryCompat() {} + + /** + * Creates an inventory with a title using the universal legacy string API. + * Works on all Bukkit/Spigot/Paper versions; the title may contain § color codes. + */ + public static Inventory createInventory(InventoryHolder holder, int size, String title) { + return Bukkit.createInventory(holder, size, title); + } + + /** + * Gets the inventory title from an {@link InventoryEvent} via reflection, compatible + * across all API versions (InventoryView was a class until 1.20.6; it became an + * interface in 1.21). + * + * @throws RuntimeException if reflection fails unexpectedly. + */ + public static String getInventoryTitle(InventoryEvent event) { + try { + Object view = event.getView(); + Method getTitle = view.getClass().getMethod("getTitle"); + getTitle.setAccessible(true); + return (String) getTitle.invoke(view); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/HeadCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/HeadCompat.java new file mode 100644 index 0000000..5278fb9 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/HeadCompat.java @@ -0,0 +1,89 @@ +package com.gyvex.ezafk.compatibility.item; + +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class HeadCompat { + + private HeadCompat() {} + + /** + * Creates a player-head {@link ItemStack} compatible with modern ({@code PLAYER_HEAD}) + * and legacy ({@code SKULL_ITEM} with data 3) Bukkit versions. + */ + public static ItemStack createPlayerHead() { + Material material = MaterialCompat.resolveMaterial("PLAYER_HEAD", "SKULL_ITEM"); + ItemStack head = new ItemStack(material); + if ("SKULL_ITEM".equals(material.name())) { + setLegacySkullData(head); + } + return head; + } + + /** + * Returns {@code true} if the given {@link ItemStack} is a player head in any + * supported API version. + */ + public static boolean isPlayerHead(ItemStack item) { + if (item == null) return false; + Material type = item.getType(); + if ("PLAYER_HEAD".equals(type.name())) return true; + if ("SKULL_ITEM".equals(type.name())) return isLegacyPlayerHead(item); + return false; + } + + /** + * Sets the owning player on a {@link SkullMeta}, trying the modern + * {@code setOwningPlayer} API first and falling back to {@code setOwner} by name. + */ + public static void setSkullOwner(SkullMeta meta, OfflinePlayer player) { + if (meta == null || player == null) return; + try { + Method setOwningPlayer = meta.getClass().getMethod("setOwningPlayer", OfflinePlayer.class); + setOwningPlayer.invoke(meta, player); + return; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + try { + Method setOwner = meta.getClass().getMethod("setOwner", String.class); + if (player.getName() != null) { + setOwner.invoke(meta, player.getName()); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + } + + /** Sets legacy skull data (data byte 3) on a SKULL_ITEM stack via reflection. */ + private static void setLegacySkullData(ItemStack item) { + try { + Class materialDataClass = Class.forName("org.bukkit.material.MaterialData"); + Object skullData = materialDataClass + .getConstructor(Material.class, byte.class) + .newInstance(item.getType(), (byte) 3); + ItemStack.class.getMethod("setData", materialDataClass).invoke(item, skullData); + } catch (Exception e) { + try { + ItemStack.class.getMethod("setDurability", short.class).invoke(item, (short) 3); + } catch (Exception ignored) {} + } + } + + /** Returns {@code true} if the SKULL_ITEM stack has the player-head data byte (3). */ + private static boolean isLegacyPlayerHead(ItemStack item) { + try { + Object data = ItemStack.class.getMethod("getData").invoke(item); + if (data != null && data.getClass().getMethod("getData").invoke(data).equals((byte) 3)) { + return true; + } + } catch (Exception e) { + try { + Object durability = ItemStack.class.getMethod("getDurability").invoke(item); + return Short.valueOf((short) 3).equals(durability); + } catch (Exception ignored) {} + } + return false; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/ItemMetaCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/ItemMetaCompat.java new file mode 100644 index 0000000..0c56aa5 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/ItemMetaCompat.java @@ -0,0 +1,139 @@ +package com.gyvex.ezafk.compatibility.item; + +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.ChatColor; +import org.bukkit.inventory.meta.ItemMeta; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public final class ItemMetaCompat { + + private ItemMetaCompat() {} + + private static final String META_PREFIX = ChatColor.DARK_GRAY + "ezafk:"; + + private static final boolean PERSISTENT_DATA_AVAILABLE = + isClassAvailable("org.bukkit.persistence.PersistentDataContainer") + && hasMethod(ItemMeta.class, "getPersistentDataContainer"); + + /** + * Stores a custom key/value pair on an {@link ItemMeta}. + * Uses the {@code PersistentDataContainer} API when available (1.14+); + * falls back to hidden lore on older versions. + */ + public static void setItemMetadata(ItemMeta meta, String key, String value) { + if (meta == null) return; + if (PERSISTENT_DATA_AVAILABLE && setPersistentData(meta, key, value)) return; + setLoreMetadata(meta, key, value); + } + + /** + * Reads a custom key/value pair from an {@link ItemMeta}. + * Checks the {@code PersistentDataContainer} first, then falls back to lore. + * + * @return The stored value, or {@code null} if not found. + */ + public static String getItemMetadata(ItemMeta meta, String key) { + if (meta == null) return null; + if (PERSISTENT_DATA_AVAILABLE) { + String value = getPersistentData(meta, key); + if (value != null) return value; + } + return getLoreMetadata(meta, key); + } + + // ------------------------------------------------------------------------- + // PersistentDataContainer (1.14+) + // ------------------------------------------------------------------------- + + private static boolean setPersistentData(ItemMeta meta, String key, String value) { + try { + Object namespacedKey = createNamespacedKey(key); + if (namespacedKey == null) return false; + Object container = meta.getClass().getMethod("getPersistentDataContainer").invoke(meta); + Class pdt = Class.forName("org.bukkit.persistence.PersistentDataType"); + Object stringType = pdt.getField("STRING").get(null); + container.getClass() + .getMethod("set", namespacedKey.getClass(), pdt, Object.class) + .invoke(container, namespacedKey, stringType, value); + return true; + } catch (Exception ignored) { + return false; + } + } + + private static String getPersistentData(ItemMeta meta, String key) { + try { + Object namespacedKey = createNamespacedKey(key); + if (namespacedKey == null) return null; + Object container = meta.getClass().getMethod("getPersistentDataContainer").invoke(meta); + Class pdt = Class.forName("org.bukkit.persistence.PersistentDataType"); + Object stringType = pdt.getField("STRING").get(null); + Object value = container.getClass() + .getMethod("get", namespacedKey.getClass(), pdt) + .invoke(container, namespacedKey, stringType); + return value instanceof String ? (String) value : null; + } catch (Exception ignored) { + return null; + } + } + + private static Object createNamespacedKey(String key) { + try { + Class nkClass = Class.forName("org.bukkit.NamespacedKey"); + Constructor ctor = nkClass.getConstructor(org.bukkit.plugin.Plugin.class, String.class); + return ctor.newInstance(Registry.get().getPlugin(), key); + } catch (Exception ignored) { + return null; + } + } + + // ------------------------------------------------------------------------- + // Lore fallback (pre-1.14) + // ------------------------------------------------------------------------- + + private static void setLoreMetadata(ItemMeta meta, String key, String value) { + String prefix = META_PREFIX + key + ":"; + List lore = meta.hasLore() ? new ArrayList<>(meta.getLore()) : new ArrayList<>(); + for (Iterator it = lore.iterator(); it.hasNext(); ) { + String line = it.next(); + if (line != null && line.startsWith(prefix)) it.remove(); + } + lore.add(prefix + value); + meta.setLore(lore); + } + + private static String getLoreMetadata(ItemMeta meta, String key) { + if (!meta.hasLore()) return null; + String prefix = META_PREFIX + key + ":"; + for (String line : meta.getLore()) { + if (line != null && line.startsWith(prefix)) return line.substring(prefix.length()); + } + return null; + } + + // ------------------------------------------------------------------------- + // Reflection helpers (local to this class) + // ------------------------------------------------------------------------- + + private static boolean isClassAvailable(String name) { + try { + Class.forName(name); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private static boolean hasMethod(Class type, String name) { + for (Method method : type.getMethods()) { + if (method.getName().equals(name)) return true; + } + return false; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/MaterialCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/MaterialCompat.java new file mode 100644 index 0000000..1f13bb3 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/item/MaterialCompat.java @@ -0,0 +1,45 @@ +package com.gyvex.ezafk.compatibility.item; + +import org.bukkit.Material; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class MaterialCompat { + + private MaterialCompat() {} + + /** + * Resolves a {@link Material} by modern name first, then legacy name. + * Uses {@code Material.matchMaterial} when available, otherwise {@code Material.valueOf}. + * + * @param modernName The modern material name (e.g. {@code "PLAYER_HEAD"}). + * @param legacyName The legacy material name fallback (e.g. {@code "SKULL_ITEM"}), may be null. + * @return The resolved {@link Material}. + * @throws IllegalArgumentException if neither name resolves to a valid material. + */ + public static Material resolveMaterial(String modernName, String legacyName) { + Material material = matchMaterial(modernName); + if (material == null && legacyName != null) { + material = matchMaterial(legacyName); + } + if (material == null) { + throw new IllegalArgumentException( + "Unable to resolve material for '" + modernName + "' or '" + legacyName + "'"); + } + return material; + } + + private static Material matchMaterial(String name) { + if (name == null) return null; + try { + Method matchMaterial = Material.class.getMethod("matchMaterial", String.class); + return (Material) matchMaterial.invoke(null, name); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {} + try { + return Material.valueOf(name); + } catch (IllegalArgumentException ignored) { + return null; + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/platform/PlatformDetector.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/platform/PlatformDetector.java new file mode 100644 index 0000000..bbc7e2d --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/platform/PlatformDetector.java @@ -0,0 +1,28 @@ +package com.gyvex.ezafk.compatibility.platform; + +/** + * Detects the server platform at runtime so the rest of the codebase can + * branch between Bukkit/Paper and Folia without duplicating the check. + */ +public final class PlatformDetector { + + private static final boolean FOLIA; + + static { + boolean folia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + } catch (ClassNotFoundException e) { + folia = false; + } + FOLIA = folia; + } + + private PlatformDetector() {} + + /** Returns {@code true} when the server is running Folia. */ + public static boolean isFolia() { + return FOLIA; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerDisplayCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerDisplayCompat.java new file mode 100644 index 0000000..00c6bbc --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerDisplayCompat.java @@ -0,0 +1,95 @@ +package com.gyvex.ezafk.compatibility.player; + +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class PlayerDisplayCompat { + + private PlayerDisplayCompat() {} + + /** + * Active BossBar instances used for kick-warning displays, keyed by player UUID. + * Stored as {@link Object} so this class compiles on pre-1.9 servers that lack + * the BossBar API. All access goes through reflection. + */ + private static final Map activeBossBars = new HashMap<>(); + + /** + * Sends a title and subtitle to a player via reflection for cross-version compatibility. + */ + public static void sendTitle(Player player, String title, String subtitle, + int fadeIn, int stay, int fadeOut) { + if (player == null) return; + try { + Method sendTitle = player.getClass().getMethod( + "sendTitle", String.class, String.class, int.class, int.class, int.class); + sendTitle.invoke(player, title, subtitle, fadeIn, stay, fadeOut); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + } + + /** + * Sends an action-bar message to a player via reflection (Paper/Spigot 1.13+). + * Silently skipped on older server versions. + */ + public static void sendActionBar(Player player, String message) { + if (player == null || message == null) return; + try { + Method sendActionBar = player.getClass().getMethod("sendActionBar", String.class); + sendActionBar.invoke(player, message); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + } + + /** + * Shows a temporary BossBar kick-warning to a player for {@code seconds} duration, + * then removes it automatically. All BossBar API access uses reflection so the + * method is safe on pre-1.9 servers; the call is silently skipped if unavailable. + */ + public static void showBossBarWarning(Player player, String message, int seconds) { + if (player == null || message == null) return; + UUID playerId = player.getUniqueId(); + removeWarningBossBar(playerId); + try { + Class barColorClass = Class.forName("org.bukkit.boss.BarColor"); + Class barStyleClass = Class.forName("org.bukkit.boss.BarStyle"); + //noinspection unchecked + Object barColor = Enum.valueOf((Class) barColorClass, "RED"); + //noinspection unchecked + Object barStyle = Enum.valueOf((Class) barStyleClass, "SOLID"); + + String title = ChatColor.translateAlternateColorCodes('&', message); + Method createBossBar = Bukkit.class.getMethod( + "createBossBar", String.class, barColorClass, barStyleClass); + Object bar = createBossBar.invoke(null, title, barColor, barStyle); + + bar.getClass().getMethod("addPlayer", Player.class).invoke(bar, player); + activeBossBars.put(playerId, bar); + + long ticksToRemove = Math.max(1L, (long) seconds * 20L); + Registry.get().getScheduler().runTaskLater( + () -> removeWarningBossBar(playerId), + ticksToRemove); + } catch (ClassNotFoundException | NoSuchMethodException + | IllegalAccessException | InvocationTargetException ignored) {} + } + + /** + * Removes and hides the active BossBar kick-warning for a player, if any. + * + * @param playerId UUID of the player whose BossBar should be removed. + */ + public static void removeWarningBossBar(UUID playerId) { + Object bar = activeBossBars.remove(playerId); + if (bar == null) return; + try { + bar.getClass().getMethod("removeAll").invoke(bar); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerKickCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerKickCompat.java new file mode 100644 index 0000000..222ff98 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/player/PlayerKickCompat.java @@ -0,0 +1,42 @@ +package com.gyvex.ezafk.compatibility.player; + +import org.bukkit.entity.Player; + +import java.lang.reflect.InvocationTargetException; + +public final class PlayerKickCompat { + + private PlayerKickCompat() {} + + /** + * Kicks a player using the most compatible method for the server version. + * Uses {@code Player.kick(String, PlayerKickEvent.Cause)} when available, + * otherwise falls back to the legacy {@code Player.kickPlayer(String)}. + * + * @param player The player to kick. + * @param message The kick message. + */ + public static void kickPlayer(Player player, String message) { + try { + Class causeClass = null; + try { + causeClass = Class.forName("org.bukkit.event.player.PlayerKickEvent$Cause"); + } catch (ClassNotFoundException ignored) {} + + if (causeClass != null) { + Object pluginCause = null; + try { + //noinspection unchecked + pluginCause = Enum.valueOf((Class) causeClass, "PLUGIN"); + } catch (Exception ignored) {} + if (pluginCause != null) { + player.getClass().getMethod("kick", String.class, causeClass) + .invoke(player, message, pluginCause); + return; + } + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + + player.kickPlayer(message); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/BukkitSchedulerAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/BukkitSchedulerAdapter.java new file mode 100644 index 0000000..9783243 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/BukkitSchedulerAdapter.java @@ -0,0 +1,50 @@ +package com.gyvex.ezafk.compatibility.scheduler; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +/** {@link SchedulerAdapter} backed by the Bukkit scheduler (Spigot / Paper). */ +public final class BukkitSchedulerAdapter implements SchedulerAdapter { + + private final Plugin plugin; + + public BukkitSchedulerAdapter(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public TaskHandle runTaskTimer(Runnable task, long delayTicks, long periodTicks) { + BukkitTask t = plugin.getServer().getScheduler() + .runTaskTimer(plugin, task, delayTicks, periodTicks); + return wrap(t); + } + + @Override + public TaskHandle runTaskTimerAsync(Runnable task, long delayTicks, long periodTicks) { + // Use BukkitRunnable so that MockBukkit (and Bukkit's own scheduler) can + // track the task by identity and honour cancel() correctly for async timers. + BukkitRunnable br = new BukkitRunnable() { + @Override public void run() { task.run(); } + }; + BukkitTask t = br.runTaskTimerAsynchronously(plugin, delayTicks, periodTicks); + return wrap(t); + } + + @Override + public void runTaskAsync(Runnable task) { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, task); + } + + @Override + public void runTaskLater(Runnable task, long delayTicks) { + plugin.getServer().getScheduler().runTaskLater(plugin, task, delayTicks); + } + + private static TaskHandle wrap(BukkitTask task) { + return new TaskHandle() { + @Override public void cancel() { task.cancel(); } + @Override public boolean isCancelled() { return task.isCancelled(); } + }; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapter.java new file mode 100644 index 0000000..1b31d82 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapter.java @@ -0,0 +1,21 @@ +package com.gyvex.ezafk.compatibility.scheduler; + +/** + * Platform-agnostic scheduling facade used by EzAfk so that task scheduling + * works on both Bukkit/Paper and Folia without scattering platform-specific + * branching throughout the codebase. + */ +public interface SchedulerAdapter { + + /** Schedules a synchronous repeating task. */ + TaskHandle runTaskTimer(Runnable task, long delayTicks, long periodTicks); + + /** Schedules an asynchronous repeating task. */ + TaskHandle runTaskTimerAsync(Runnable task, long delayTicks, long periodTicks); + + /** Runs a one-shot task asynchronously (fire-and-forget). */ + void runTaskAsync(Runnable task); + + /** Runs a one-shot task synchronously after {@code delayTicks} ticks. */ + void runTaskLater(Runnable task, long delayTicks); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapterFactory.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapterFactory.java new file mode 100644 index 0000000..5c6d200 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapterFactory.java @@ -0,0 +1,42 @@ +package com.gyvex.ezafk.compatibility.scheduler; + +import com.gyvex.ezafk.compatibility.platform.PlatformDetector; +import org.bukkit.plugin.Plugin; + +/** + * Creates the best {@link SchedulerAdapter} for the current server platform. + * + *
    + *
  • Folia: {@link FoliaSchedulerAdapter} (GlobalRegionScheduler + + * AsyncScheduler)
  • + *
  • Paper / Spigot: {@link BukkitSchedulerAdapter}
  • + *
+ * + *

If Folia is detected but its API classes are absent at runtime, a warning + * is logged and the factory falls back to {@link BukkitSchedulerAdapter}. + */ +public final class SchedulerAdapterFactory { + + private SchedulerAdapterFactory() {} + + public static SchedulerAdapter create(Plugin plugin) { + if (PlatformDetector.isFolia()) { + try { + return createFoliaSchedulerAdapter(plugin); + } catch (NoClassDefFoundError e) { + plugin.getLogger().warning("[EzAfk] Folia classes not found, falling back to Bukkit scheduler: " + e.getMessage()); + } catch (ClassNotFoundException e) { + plugin.getLogger().warning("[EzAfk] FoliaSchedulerAdapter class not found, falling back to Bukkit scheduler."); + } catch (Throwable e) { + plugin.getLogger().warning("[EzAfk] Folia detected but Folia scheduler API unavailable — falling back to Bukkit scheduler: " + e.getMessage()); + } + } + return new BukkitSchedulerAdapter(plugin); + } + + private static SchedulerAdapter createFoliaSchedulerAdapter(Plugin plugin) throws ClassNotFoundException, ReflectiveOperationException { + Class clazz = Class.forName("com.gyvex.ezafk.compatibility.scheduler.FoliaSchedulerAdapter"); + java.lang.reflect.Constructor ctor = clazz.getConstructor(Plugin.class); + return (SchedulerAdapter) ctor.newInstance(plugin); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/TaskHandle.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/TaskHandle.java new file mode 100644 index 0000000..3ba1f6f --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/scheduler/TaskHandle.java @@ -0,0 +1,7 @@ +package com.gyvex.ezafk.compatibility.scheduler; + +/** A cancellable handle to a scheduled repeating or delayed task. */ +public interface TaskHandle { + void cancel(); + boolean isCancelled(); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/sound/SoundCompat.java b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/sound/SoundCompat.java new file mode 100644 index 0000000..8ca4576 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/compatibility/sound/SoundCompat.java @@ -0,0 +1,48 @@ +package com.gyvex.ezafk.compatibility.sound; + +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class SoundCompat { + + private SoundCompat() {} + + /** + * Plays a sound for a player, trying each provided name in order until one succeeds. + * First attempts {@link Sound#valueOf} (modern enum lookup); if none of the names + * match a known enum constant the first non-empty name is played via the string-based + * {@code playSound} reflection path for legacy / custom sound support. + * + * @param player The target player. + * @param volume Sound volume. + * @param pitch Sound pitch. + * @param soundNames One or more sound name candidates (modern name first). + */ + public static void playSound(Player player, float volume, float pitch, String... soundNames) { + if (player == null || soundNames == null) return; + + for (String soundName : soundNames) { + if (soundName == null || soundName.isEmpty()) continue; + try { + Sound sound = Sound.valueOf(soundName); + player.playSound(player.getLocation(), sound, volume, pitch); + return; + } catch (IllegalArgumentException ignored) {} + } + + // Fallback: string-based playSound (covers custom resource-pack sounds) + for (String soundName : soundNames) { + if (soundName == null || soundName.isEmpty()) continue; + try { + Method playSound = player.getClass().getMethod( + "playSound", player.getLocation().getClass(), String.class, + float.class, float.class); + playSound.invoke(player, player.getLocation(), soundName, volume, pitch); + return; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {} + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/config/ConfigManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/config/ConfigManager.java new file mode 100644 index 0000000..5f7f568 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/config/ConfigManager.java @@ -0,0 +1,264 @@ +package com.gyvex.ezafk.config; + +import com.gyvex.ezafk.EzAfk; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import com.gyvex.ezafk.manager.EconomyManager; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.logging.Level; + +public class ConfigManager { + private final EzAfk plugin; + public FileConfiguration config; + private boolean afkSoundEnabled; + private String afkSoundFile; + private boolean unafkSoundEnabled; + private String unafkSoundFile; + private boolean debugEnabled; + private FileConfiguration messagesConfig; + private FileConfiguration guiConfig; + private FileConfiguration mysqlConfig; + private File messagesFile; + private File guiFile; + private File mysqlFile; + + public ConfigManager(EzAfk plugin) { + this.plugin = plugin; + } + + public FileConfiguration loadConfig() { + plugin.reloadConfig(); + this.config = plugin.getConfig(); + saveDefaultGuiConfig(); + reloadGuiConfig(); + saveDefaultMysqlConfig(); + reloadMysqlConfig(); + saveDefaultMessages(); + reloadMessages(); + EconomyManager.reset(); + + // Load debug config + debugEnabled = config.getBoolean("debug", false); + + // Load AFK sound config + afkSoundEnabled = config.getBoolean("afk.sound.enabled", true); + afkSoundFile = config.getString("afk.sound.file", "plugins/EzAfk/afk-sound.mp3"); + // Load return-from-AFK sound config + unafkSoundEnabled = config.getBoolean("unafk.sound.enabled", true); + unafkSoundFile = config.getString("unafk.sound.file", "mp3/ezafk-sound.mp3"); + return this.config; + } + + public boolean isDebugEnabled() { + return debugEnabled; + } + + public boolean isAfkSoundEnabled() { + return afkSoundEnabled; + } + + public String getAfkSoundFile() { + return afkSoundFile; + } + + public boolean isUnafkSoundEnabled() { + return unafkSoundEnabled; + } + + public String getUnafkSoundFile() { + return unafkSoundFile; + } + + public FileConfiguration getMessages() { + if (this.messagesConfig == null) { + reloadMessages(); + } + + return messagesConfig; + } + + public FileConfiguration getGuiConfig() { + if (this.guiConfig == null) { + reloadGuiConfig(); + } + + return guiConfig; + } + + public FileConfiguration getMysqlConfig() { + if (this.mysqlConfig == null) { + reloadMysqlConfig(); + } + + return mysqlConfig; + } + + public void reloadMessages() { + String fileName = getMessagesFileName(); + + saveDefaultMessages(fileName); + + messagesConfig = YamlConfiguration.loadConfiguration(messagesFile); + + InputStream defaultStream = plugin.getResource(fileName); + + if (defaultStream != null) { + try (InputStreamReader reader = new InputStreamReader(defaultStream, StandardCharsets.UTF_8)) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(reader); + messagesConfig.setDefaults(defaultConfig); + messagesConfig.options().copyDefaults(true); + } catch (IOException exception) { + plugin.getLogger().log(Level.WARNING, "Failed to load default " + fileName, exception); + } + } + } + + private void saveDefaultMessages() { + saveDefaultMessages(getMessagesFileName()); + } + + private void saveDefaultMessages(String fileName) { + File dataFolder = plugin.getDataFolder(); + File targetFile = new File(dataFolder, fileName); + + if (messagesFile == null || !messagesFile.equals(targetFile)) { + messagesFile = targetFile; + } + + File parent = messagesFile.getParentFile(); + if (parent != null && !parent.exists()) { + // Ensure the messages directory exists before attempting to save resources inside it. + parent.mkdirs(); + } + + if (!messagesFile.exists()) { + plugin.saveResource(fileName, false); + } + } + + private String getMessagesFileName() { + String language = "en"; + + if (config != null) { + String configured = config.getString("messages.language", "en"); + + if (configured != null) { + language = configured.trim().toLowerCase(Locale.ROOT); + } + } + + switch (language) { + case "es": + return resolveMessagesFileName("es", "messages_es.yml"); + case "nl": + return resolveMessagesFileName("nl", "messages_nl.yml"); + case "ru": + return resolveMessagesFileName("ru", "messages_ru.yml"); + case "zh": + return resolveMessagesFileName("zh", "messages_zh.yml"); + case "ja": + return resolveMessagesFileName("ja", "messages_ja.yml"); + case "en": + return resolveEnglishMessagesFileName(); + default: + // Allow custom language codes (for example 'fr', 'de', 'pt', etc.). + // The plugin will look for messages/messages_.yml in the + // plugin data folder, or copy the bundled resource if it exists. + return resolveMessagesFileName(language); + } + } + + private String resolveEnglishMessagesFileName() { + File dataFolder = plugin.getDataFolder(); + return resolveMessagesFileName("en", "messages.yml", "messages_en.yml"); + } + + private String resolveMessagesFileName(String language, String... legacyFileNames) { + File dataFolder = plugin.getDataFolder(); + String newFileName = "messages/" + (language.equals("en") ? "messages" : "messages_" + language) + ".yml"; + + File newFile = new File(dataFolder, newFileName); + if (newFile.exists()) { + return newFileName; + } + + for (String legacyFileName : legacyFileNames) { + File legacyFile = new File(dataFolder, legacyFileName); + if (legacyFile.exists()) { + return legacyFileName; + } + } + + return newFileName; + } + + public void reloadGuiConfig() { + saveDefaultGuiConfig(); + + if (guiFile == null) { + guiFile = new File(plugin.getDataFolder(), "gui.yml"); + } + + guiConfig = YamlConfiguration.loadConfiguration(guiFile); + + InputStream defaultStream = plugin.getResource("gui.yml"); + + if (defaultStream != null) { + try (InputStreamReader reader = new InputStreamReader(defaultStream, StandardCharsets.UTF_8)) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(reader); + guiConfig.setDefaults(defaultConfig); + guiConfig.options().copyDefaults(true); + } catch (IOException exception) { + plugin.getLogger().log(Level.WARNING, "Failed to load default gui.yml", exception); + } + } + } + + private void saveDefaultGuiConfig() { + if (guiFile == null) { + guiFile = new File(plugin.getDataFolder(), "gui.yml"); + } + + if (!guiFile.exists()) { + plugin.saveResource("gui.yml", false); + } + } + + public void reloadMysqlConfig() { + saveDefaultMysqlConfig(); + + if (mysqlFile == null) { + mysqlFile = new File(plugin.getDataFolder(), "mysql.yml"); + } + + mysqlConfig = YamlConfiguration.loadConfiguration(mysqlFile); + + InputStream defaultStream = plugin.getResource("mysql.yml"); + + if (defaultStream != null) { + try (InputStreamReader reader = new InputStreamReader(defaultStream, StandardCharsets.UTF_8)) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(reader); + mysqlConfig.setDefaults(defaultConfig); + mysqlConfig.options().copyDefaults(true); + } catch (IOException exception) { + plugin.getLogger().log(Level.WARNING, "Failed to load default mysql.yml", exception); + } + } + } + + private void saveDefaultMysqlConfig() { + if (mysqlFile == null) { + mysqlFile = new File(plugin.getDataFolder(), "mysql.yml"); + } + + if (!mysqlFile.exists()) { + plugin.saveResource("mysql.yml", false); + } + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/event/PlayerAfkStatusChangeEvent.java b/ezafk-core/src/main/java/com/gyvex/ezafk/event/PlayerAfkStatusChangeEvent.java new file mode 100644 index 0000000..5da1766 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/event/PlayerAfkStatusChangeEvent.java @@ -0,0 +1,69 @@ +package com.gyvex.ezafk.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import com.gyvex.ezafk.state.AfkReason; + +/** + * Custom event fired when a player's AFK status changes in EzAfk. + * Other plugins can listen to this event to react to AFK/return actions. + */ +public class PlayerAfkStatusChangeEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + private final Player player; + private final boolean isAfk; + private final AfkReason reason; + private final String detail; + private boolean cancelled; + + /** + * @param player The player whose AFK status changed + * @param isAfk True if the player is now AFK, false if they returned + * @param reason The reason for AFK status change + * @param detail Additional details about the change + */ + public PlayerAfkStatusChangeEvent(Player player, boolean isAfk, AfkReason reason, String detail) { + this.player = player; + this.isAfk = isAfk; + this.reason = reason; + this.detail = detail; + this.cancelled = false; + } + + public Player getPlayer() { + return player; + } + + public boolean isAfk() { + return isAfk; + } + + public AfkReason getReason() { + return reason; + } + + public String getDetail() { + return detail; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiAction.java b/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiAction.java new file mode 100644 index 0000000..31440a0 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiAction.java @@ -0,0 +1,96 @@ +package com.gyvex.ezafk.gui; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.ChatColor; +import java.util.List; + +public class GuiAction { + private final Material material; + private final String displayName; + private final List lore; + private final ActionType type; + private final String targetMessage; + private final String feedbackMessage; + private final String command; + + public GuiAction(Material material, String displayName, List lore, ActionType type, String targetMessage, String feedbackMessage, String command) { + this.material = material; + this.displayName = displayName; + this.lore = lore; + this.type = type; + this.targetMessage = targetMessage; + this.feedbackMessage = feedbackMessage; + this.command = command; + } + + public ItemStack createIcon() { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + com.gyvex.ezafk.compatibility.LoreUtil.setDisplayName(meta, displayName, null, org.bukkit.Bukkit.getLogger()); + if (lore != null) { + com.gyvex.ezafk.compatibility.LoreUtil.setLore(meta, lore, null, org.bukkit.Bukkit.getLogger()); + } + item.setItemMeta(meta); + } + return item; + } + + public void execute(Player executor, Player target) { + switch (type) { + case KICK: + if (target == null) { + com.gyvex.ezafk.manager.MessageManager.sendMessage(executor, "gui.actions.error.player-not-found", "&cCould not find the selected player."); + return; + } + String kickMessage = com.gyvex.ezafk.util.PlaceholderUtil.colorize(com.gyvex.ezafk.util.PlaceholderUtil.replacePlaceholders(executor, target, targetMessage)); + String defaultKickMessage = com.gyvex.ezafk.manager.MessageManager.getMessage("gui.actions.default-kick-message", "&cYou were kicked for being AFK too long."); + target.kickPlayer(kickMessage != null ? kickMessage : defaultKickMessage); + break; + case MESSAGE: + if (target == null) { + com.gyvex.ezafk.manager.MessageManager.sendMessage(executor, "gui.actions.error.player-not-found", "&cCould not find the selected player."); + return; + } + String alertMessage = com.gyvex.ezafk.util.PlaceholderUtil.colorize(com.gyvex.ezafk.util.PlaceholderUtil.replacePlaceholders(executor, target, targetMessage)); + if (alertMessage != null) { + target.sendMessage(alertMessage); + } + break; + case TELEPORT: + if (target == null) { + com.gyvex.ezafk.manager.MessageManager.sendMessage(executor, "gui.actions.error.player-not-found", "&cCould not find the selected player."); + return; + } + executor.teleport(target); + break; + case COMMAND: + String commandToRun = com.gyvex.ezafk.util.PlaceholderUtil.replacePlaceholders(executor, target, command); + if (commandToRun == null || commandToRun.isEmpty()) { + com.gyvex.ezafk.manager.MessageManager.sendMessage(executor, "gui.actions.error.no-command", "&cNo command configured for this action."); + return; + } + org.bukkit.Bukkit.dispatchCommand(org.bukkit.Bukkit.getConsoleSender(), commandToRun); + break; + default: + return; + } + + String feedback = com.gyvex.ezafk.util.PlaceholderUtil.colorize(com.gyvex.ezafk.util.PlaceholderUtil.replacePlaceholders(executor, target, feedbackMessage)); + if (feedback != null && !feedback.isEmpty()) { + executor.sendMessage(feedback); + } + + executor.closeInventory(); + } + + public enum ActionType { + KICK, + MESSAGE, + TELEPORT, + COMMAND + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiActionFactory.java b/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiActionFactory.java new file mode 100644 index 0000000..8dfbef2 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/gui/GuiActionFactory.java @@ -0,0 +1,31 @@ +package com.gyvex.ezafk.gui; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import java.util.List; + +public class GuiActionFactory { + public static GuiAction fromConfigSection(ConfigurationSection section) { + String materialName = section.getString("material", "STONE"); + Material material = Material.matchMaterial(materialName); + if (material == null) { + material = Material.matchMaterial(materialName.toUpperCase()); + } + if (material == null) { + material = Material.STONE; + } + String displayName = section.getString("display-name", "Action"); + List lore = section.getStringList("lore"); + String typeName = section.getString("type", "MESSAGE"); + GuiAction.ActionType actionType; + try { + actionType = GuiAction.ActionType.valueOf(typeName.toUpperCase()); + } catch (IllegalArgumentException e) { + actionType = GuiAction.ActionType.MESSAGE; + } + String targetMessage = section.getString("target-message"); + String feedbackMessage = section.getString("feedback-message"); + String command = section.getString("command"); + return new GuiAction(material, displayName, lore, actionType, targetMessage, feedbackMessage, command); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/EconomyIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/EconomyIntegration.java new file mode 100644 index 0000000..11465e1 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/EconomyIntegration.java @@ -0,0 +1,79 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class EconomyIntegration extends Integration { + private Economy economy; + + public Economy getEconomy() { + return economy; + } + + @Override + public void load() { + this.isSetup = false; + + EzAfk plugin = Registry.get().getPlugin(); + + clearEconomy(plugin, "Economy integration cleared before Vault detection."); + + if (plugin == null || plugin.getConfig() == null) { + return; + } + + if (!plugin.getConfig().getBoolean("economy.enabled")) { + return; + } + + if (!Bukkit.getPluginManager().isPluginEnabled("Vault")) { + clearEconomy(plugin, "Economy integration cleared because Vault is not enabled."); + plugin.getLogger().log(Level.WARNING, "Economy support requires Vault, but it was not found. Disabling economy features."); + return; + } + + RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(Economy.class); + + if (provider == null) { + clearEconomy(plugin, "Economy integration cleared because no Vault economy provider was detected."); + plugin.getLogger().log(Level.WARNING, "Vault is installed but no economy provider was detected. Disabling economy features."); + return; + } + + Economy detectedEconomy = provider.getProvider(); + + if (detectedEconomy == null) { + clearEconomy(plugin, "Economy integration cleared because the Vault provider returned no economy implementation."); + plugin.getLogger().log(Level.WARNING, "Failed to access the Vault economy provider. Disabling economy features."); + return; + } + + this.economy = detectedEconomy; + this.isSetup = true; + String providerName = provider.getProvider().getName(); + plugin.getLogger().log(Level.INFO, "Economy integration enabled via Vault. Found provider: " + providerName); + } + + @Override + public void unload() { + EzAfk plugin = Registry.get().getPlugin(); + clearEconomy(plugin, "Economy integration cleared during unload."); + this.isSetup = false; + } + + private void clearEconomy(EzAfk plugin, String reason) { + this.economy = null; + + Logger logger = plugin != null ? plugin.getLogger() : Bukkit.getLogger(); + + if (logger != null && reason != null && !reason.isBlank()) { + logger.log(Level.INFO, reason); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/Integration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/Integration.java new file mode 100644 index 0000000..e3c47ef --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/Integration.java @@ -0,0 +1,7 @@ +package com.gyvex.ezafk.integration; + +public abstract class Integration { + public boolean isSetup; + public abstract void load(); + public abstract void unload(); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/MetricsIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/MetricsIntegration.java new file mode 100644 index 0000000..cf96eb3 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/MetricsIntegration.java @@ -0,0 +1,42 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.manager.IntegrationManager; +import com.gyvex.ezafk.state.AfkState; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.SingleLineChart; + +public class MetricsIntegration extends Integration { + private Metrics metrics; + + private void setupMetrics() { + this.metrics = new Metrics(Registry.get().getPlugin(), 22316); + this.metrics.addCustomChart(new SingleLineChart("tab_integration_count", () -> IntegrationManager.hasIntegration("tab") ? 1 : 0)); + this.metrics.addCustomChart(new SingleLineChart("worldguard_integration_count", () -> IntegrationManager.hasIntegration("worldguard") ? 1 : 0)); + this.metrics.addCustomChart(new SingleLineChart("afk_players", () -> AfkState.afkPlayers.size())); + } + + public Metrics getMetrics() { + return this.metrics; + } + + @Override + public void load() { + try { + this.setupMetrics(); + this.isSetup = true; + } catch (IllegalStateException | NoClassDefFoundError ex) { + Registry.get().getLogger().warning("bStats Metrics unavailable or not relocated; skipping metrics setup in this environment."); + this.metrics = null; + this.isSetup = false; + } + } + + @Override + public void unload() { + if (metrics != null) { + metrics.shutdown(); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlaceholderApiIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlaceholderApiIntegration.java new file mode 100644 index 0000000..8f7fc95 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlaceholderApiIntegration.java @@ -0,0 +1,43 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.integration.placeholder.EzAfkPlaceholderExpansion; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.util.logging.Level; + +public class PlaceholderApiIntegration extends Integration { + + private EzAfkPlaceholderExpansion expansion; + + @Override + public void load() { + Plugin placeholderApi = Bukkit.getPluginManager().getPlugin("PlaceholderAPI"); + + if (placeholderApi == null || !placeholderApi.isEnabled()) { + Registry.get().getLogger().log(Level.FINE, "PlaceholderAPI not detected. Skipping expansion registration."); + return; + } + + expansion = new EzAfkPlaceholderExpansion(); + + if (expansion.register()) { + isSetup = true; + Registry.get().getLogger().log(Level.INFO, "Registered EzAfk placeholders with PlaceholderAPI."); + } else { + Registry.get().getLogger().log(Level.WARNING, "Failed to register EzAfk placeholders with PlaceholderAPI."); + } + } + + @Override + public void unload() { + if (expansion != null) { + expansion.unregister(); + expansion = null; + } + + isSetup = false; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlayerListNameAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlayerListNameAdapter.java new file mode 100644 index 0000000..c84689a --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/PlayerListNameAdapter.java @@ -0,0 +1,16 @@ +package com.gyvex.ezafk.integration; + +import java.util.UUID; +import org.bukkit.entity.Player; + +public interface PlayerListNameAdapter { + void removeInvalidEntries(); + + String getBaseName(Player player); + + boolean apply(Player player, String targetName); + + boolean restore(UUID uuid); + + void restoreAll(); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SimpleVoiceChatIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SimpleVoiceChatIntegration.java new file mode 100644 index 0000000..3ef07fc --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SimpleVoiceChatIntegration.java @@ -0,0 +1,5 @@ + +package com.gyvex.ezafk.integration; + +// Wrapper removed. Use com.gyvex.ezafk.integration.voicechat.SimpleVoiceChatIntegration instead. + diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SpigotIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SpigotIntegration.java new file mode 100644 index 0000000..3d1d993 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/SpigotIntegration.java @@ -0,0 +1,36 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.integration.spigot.UpdateChecker; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.logging.Logger; + +public class SpigotIntegration extends Integration { + @Override + public void load() { + if (Registry.get().getPlugin().getConfig().getBoolean("integration.spigot.check-for-update")) { + // Capture synchronously — the consumer runs async, after Registry may be torn down + final JavaPlugin plugin = Registry.get().getPlugin(); + final Logger logger = Registry.get().getLogger(); + final String currentVersion = plugin.getDescription().getVersion(); + + logger.info("Initiating update check..."); + + new UpdateChecker(plugin, 117430).getVersion(version -> { + if (currentVersion.equals(version)) { + logger.info("You are running on the latest version."); + } else { + logger.info("A new version (" + version + ") of EzAfk is available."); + logger.info("Please visit the following link to download the latest update:"); + logger.info("https://modrinth.com/plugin/ezafk"); + } + }); + } + } + + @Override + public void unload() { + + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/TabIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/TabIntegration.java new file mode 100644 index 0000000..738e332 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/TabIntegration.java @@ -0,0 +1,561 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.state.AfkState; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import me.neznamy.tab.api.TabAPI; +import java.util.function.Consumer; +import java.lang.reflect.InvocationTargetException; +import me.neznamy.tab.api.TabPlayer; +import me.neznamy.tab.api.placeholder.PlaceholderManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class TabIntegration extends Integration { + private static final int PLAYER_LIST_NAME_LIMIT = 80; + private static final String TAB_AFK_PLACEHOLDER = "%afk%"; + private static final int TAB_AFK_PLACEHOLDER_REFRESH_INTERVAL = 200; + + private boolean tabPrefixEnabled; + private String prefixTemplate; + private String suffixTemplate; + private String formatTemplate; + private TabIntegrationMode integrationMode = TabIntegrationMode.AUTO; + + private final BukkitTabNameAdapter bukkitAdapter = new BukkitTabNameAdapter(); + private PlayerListNameAdapter tabApiAdapter; + private boolean tabApiUnavailableLogged; + private boolean tabPlaceholderRegistered; + private boolean tabPlaceholderUnavailableLogged; + private int tabInitAttempts; + private static final int MAX_TAB_INIT_ATTEMPTS = 5; + private enum TabAdapterFailure { + NONE, + API_NULL, + CLASS_NOT_FOUND, + LINKAGE_ERROR, + REFLECTION_ERROR, + OTHER + } + + private TabAdapterFailure lastTabAdapterFailure = TabAdapterFailure.NONE; + private Throwable lastTabAdapterThrowable; + private boolean tabApiAvailableLogged; + + public TabIntegration() { + reloadFromConfig(); + } + + public void reloadFromConfig() { + this.tabPrefixEnabled = Registry.get().getPlugin().getConfig().getBoolean("integration.tab-prefix.enabled"); + + this.prefixTemplate = Registry.get().getPlugin().getConfig().getString("integration.tab-prefix.prefix", ""); + this.suffixTemplate = Registry.get().getPlugin().getConfig().getString("integration.tab-prefix.suffix", ""); + // The config keys live under 'integration.tab-prefix' in config.yml + this.formatTemplate = Registry.get().getPlugin().getConfig().getString("integration.tab-prefix.format", "%prefix%%player%%suffix%"); + this.integrationMode = resolveIntegrationMode(); + this.tabApiUnavailableLogged = false; + this.tabPlaceholderUnavailableLogged = false; + this.tabApiAvailableLogged = false; + } + + public void update() { + refreshTabApiAdapter(); + if (shouldUseTabPlugin()) { + ensureTabPlaceholderRegistration(); + } else { + unregisterTabPlaceholder(); + } + + if (!isIntegrationEnabled() || !tabPrefixEnabled) { + if (tabApiAdapter != null) { + tabApiAdapter.restoreAll(); + } + bukkitAdapter.restoreAll(); + unregisterTabPlaceholder(); + return; + } + + if (tabApiAdapter != null) { + tabApiAdapter.removeInvalidEntries(); + } + bukkitAdapter.removeInvalidEntries(); + + for (Player player : Bukkit.getOnlinePlayers()) { + UUID uuid = player.getUniqueId(); + if (AfkState.isAfk(uuid)) { + if (!applyUsingTabApi(player)) { + applyUsingBukkit(player); + } + } else { + if (tabApiAdapter != null) { + tabApiAdapter.restore(uuid); + } + bukkitAdapter.restore(uuid); + } + } + } + + private boolean applyUsingTabApi(Player player) { + if (tabApiAdapter == null) { + return false; + } + + String baseName = tabApiAdapter.getBaseName(player); + if (baseName == null) { + return false; + } + + UUID uuid = player.getUniqueId(); + bukkitAdapter.restore(uuid); + + String targetName = buildAfkListName(baseName); + return tabApiAdapter.apply(player, targetName); + } + + private void applyUsingBukkit(Player player) { + String baseName = bukkitAdapter.getBaseName(player); + String targetName = buildAfkListName(baseName); + bukkitAdapter.apply(player, targetName); + } + + private String buildAfkListName(String baseName) { + String format = formatTemplate; + if (format == null || format.isEmpty()) { + format = "%prefix%%player%%suffix%"; + } + + String prefix = prefixTemplate == null ? "" : prefixTemplate; + String suffix = suffixTemplate == null ? "" : suffixTemplate; + + String combined = + format.replace("%prefix%", prefix).replace("%player%", baseName).replace("%suffix%", suffix); + combined = ChatColor.translateAlternateColorCodes('&', combined); + if (combined.length() <= PLAYER_LIST_NAME_LIMIT) { + return combined; + } + + String trimmed = combined.substring(0, PLAYER_LIST_NAME_LIMIT); + if (trimmed.endsWith("§")) { + trimmed = trimmed.substring(0, trimmed.length() - 1); + } + return trimmed; + } + + private void refreshTabApiAdapter() { + if (!shouldUseTabPlugin()) { + if (tabApiAdapter != null) { + tabApiAdapter.restoreAll(); + tabApiAdapter = null; + tabApiAvailableLogged = false; + } + return; + } + + Plugin tabPlugin = Bukkit.getPluginManager().getPlugin("TAB"); + boolean tabPluginEnabled = tabPlugin != null && tabPlugin.isEnabled(); + + if (!tabPluginEnabled) { + if (tabApiAdapter != null) { + tabApiAdapter.restoreAll(); + tabApiAdapter = null; + tabApiAvailableLogged = false; + } + if (integrationMode == TabIntegrationMode.TAB && !tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.WARNING, "TAB support requested but the plugin is not installed or disabled. Falling back to Bukkit player list."); + tabApiUnavailableLogged = true; + } + return; + } + + if (tabApiAdapter == null) { + PlayerListNameAdapter adapter = createTabApiAdapter(); + if (adapter != null) { + tabApiAdapter = adapter; + tabApiUnavailableLogged = false; + tabApiAvailableLogged = false; + tabInitAttempts = 0; + + // Try to register for TAB reload events reflectively so we + // re-initialize when TAB reloads without a compile-time + // dependency on event classes. + try { + TabAPI api = TabAPI.getInstance(); + if (api != null) { + Object eventBus = api.getClass().getMethod("getEventBus").invoke(api); + if (eventBus != null) { + try { + java.lang.reflect.Method register = eventBus.getClass().getMethod( + "register", Class.class, Consumer.class); + Consumer consumer = ev -> refreshTabApiAdapter(); + // Attempt to register TabLoadEvent and PlayerLoadEvent if present + try { + Class tabLoad = Class.forName("me.neznamy.tab.api.event.TabLoadEvent"); + register.invoke(eventBus, tabLoad, consumer); + } catch (ClassNotFoundException ignored) { + } + try { + Class playerLoad = Class.forName("me.neznamy.tab.api.event.PlayerLoadEvent"); + register.invoke(eventBus, playerLoad, consumer); + } catch (ClassNotFoundException ignored) { + } + } catch (NoSuchMethodException ignored) { + // Event bus API differs; ignore. + } + } + } + } catch (LinkageError | Exception ignored) { + // Best-effort: ignore reflective registration failures. + } + // Log successful initialization once at INFO so admins see TAB integration + try { + if (!tabApiAvailableLogged) { + TabAPI api = null; + try { + api = TabAPI.getInstance(); + } catch (LinkageError | Exception ignored) { + } + String apiInfo = api == null ? "(TabAPI=null)" : api.getClass().getName(); + Registry.get().getLogger().log(Level.INFO, "TAB integration enabled using adapter {0} {1}", + new Object[]{adapter.getClass().getName(), apiInfo}); + tabApiAvailableLogged = true; + } + } catch (Exception ignored) { + } + } else if (!tabApiUnavailableLogged) { + // If TAB plugin is present but adapter failed for a non-transient + // reason, log an immediate INFO line so admins can see the cause + // without enabling FINE logs. + try { + TabAPI maybeApi = null; + try { maybeApi = TabAPI.getInstance(); } catch (Throwable ignored) {} + if (maybeApi != null && lastTabAdapterFailure != TabAdapterFailure.API_NULL) { + if (lastTabAdapterFailure == TabAdapterFailure.REFLECTION_ERROR && lastTabAdapterThrowable != null) { + Registry.get().getLogger().log(Level.INFO, + "EzAfk TAB integration unavailable: {0} (adapter class {1})", + new Object[]{lastTabAdapterFailure, (lastTabAdapterFailure == TabAdapterFailure.CLASS_NOT_FOUND ? "missing" : "present")}); + Registry.get().getLogger().log(Level.INFO, "Detailed TAB adapter error:", lastTabAdapterThrowable); + } else { + Registry.get().getLogger().log(Level.INFO, + "EzAfk TAB integration unavailable: {0} (adapter class {1})", + new Object[]{lastTabAdapterFailure, (lastTabAdapterFailure == TabAdapterFailure.CLASS_NOT_FOUND ? "missing" : "present")}); + } + } + } catch (Throwable ignored) { + } + // Distinguish between TAB plugin present but API not yet initialized + try { + // Decide logging based on the specific failure reason to avoid + // noisy warnings when TAB is merely still initializing. + if (lastTabAdapterFailure == TabAdapterFailure.API_NULL || TabAPI.getInstance() == null) { + Registry.get().getLogger().log(Level.FINE, "TAB plugin present but API returned null; deferring adapter initialization."); + // schedule a bounded retry with exponential backoff + if (tabInitAttempts < MAX_TAB_INIT_ATTEMPTS) { + long delay = 40L * (1L << tabInitAttempts); // 1s,2s,4s,... + tabInitAttempts++; + Registry.get().getScheduler().runTaskLater(this::refreshTabApiAdapter, delay); + } else { + Registry.get().getLogger().log(Level.FINE, "Exceeded TAB adapter init retries; will not retry further."); + } + } else if (lastTabAdapterFailure == TabAdapterFailure.CLASS_NOT_FOUND + || lastTabAdapterFailure == TabAdapterFailure.LINKAGE_ERROR + || lastTabAdapterFailure == TabAdapterFailure.OTHER + || lastTabAdapterFailure == TabAdapterFailure.REFLECTION_ERROR) { + // Only warn once retries are exhausted; otherwise defer silently + if (tabInitAttempts >= MAX_TAB_INIT_ATTEMPTS) { + Registry.get().getLogger().log(Level.WARNING, "TAB detected but API is unavailable. Falling back to Bukkit player list."); + // also emit a short INFO diagnostic for admins who don't use FINE + Registry.get().getLogger().log(Level.INFO, "EzAfk TAB diagnostics: lastFailure={0}, adapterClass={1}", + new Object[]{lastTabAdapterFailure, (lastTabAdapterFailure == TabAdapterFailure.CLASS_NOT_FOUND ? "missing" : "present")}); + } else { + Registry.get().getLogger().log(Level.FINE, "TAB adapter initialization failed ({0}); will retry.", lastTabAdapterFailure); + } + } else { + // Fallback conservative behavior + Registry.get().getLogger().log(Level.FINE, "TAB detected but adapter initialization failed; deferring."); + } + } catch (LinkageError | Exception ex) { + Registry.get().getLogger().log(Level.WARNING, "TAB detected but API appears unusable. Falling back to Bukkit player list."); + } + tabApiUnavailableLogged = true; + } + } + } + + private PlayerListNameAdapter createTabApiAdapter() { + try { + Class adapterClass = Class.forName("com.gyvex.ezafk.integration.tab.TabApiPlayerListNameAdapter"); + if (!PlayerListNameAdapter.class.isAssignableFrom(adapterClass)) { + lastTabAdapterFailure = TabAdapterFailure.OTHER; + return null; + } + Object instance = adapterClass.getDeclaredConstructor().newInstance(); + lastTabAdapterFailure = TabAdapterFailure.NONE; + lastTabAdapterThrowable = null; + return (PlayerListNameAdapter) instance; + } catch (ClassNotFoundException ex) { + lastTabAdapterFailure = TabAdapterFailure.CLASS_NOT_FOUND; + lastTabAdapterThrowable = ex; + if (!tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "TAB adapter class not found", ex); + } + return null; + } catch (InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof IllegalStateException && "TAB API returned null instance".equals(cause.getMessage())) { + lastTabAdapterFailure = TabAdapterFailure.API_NULL; + lastTabAdapterThrowable = cause; + } else { + lastTabAdapterFailure = TabAdapterFailure.REFLECTION_ERROR; + lastTabAdapterThrowable = ex; + } + if (!tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "Failed to initialize TAB API adapter", ex); + } + return null; + } catch (ReflectiveOperationException | RuntimeException ex) { + lastTabAdapterFailure = TabAdapterFailure.REFLECTION_ERROR; + lastTabAdapterThrowable = ex; + if (!tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "Failed to initialize TAB API adapter", ex); + } + return null; + } catch (LinkageError ex) { + lastTabAdapterFailure = TabAdapterFailure.LINKAGE_ERROR; + lastTabAdapterThrowable = ex; + if (!tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "Failed to initialize TAB API adapter", ex); + } + return null; + } catch (Throwable ex) { + lastTabAdapterFailure = TabAdapterFailure.OTHER; + lastTabAdapterThrowable = ex; + if (!tabApiUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "Failed to initialize TAB API adapter", ex); + } + return null; + } + } + + @Override + public void load() { + refreshTabApiAdapter(); + logTabDiagnostics(); + this.update(); + this.isSetup = true; + } + + private void logTabDiagnostics() { + try { + Plugin tabPlugin = Bukkit.getPluginManager().getPlugin("TAB"); + boolean present = tabPlugin != null; + boolean enabled = present && tabPlugin.isEnabled(); + TabAPI api = null; + try { + api = TabAPI.getInstance(); + } catch (LinkageError | Exception ignored) { + } + String apiInstance = api == null ? "null" : api.getClass().getName(); + String adapterClassAvailable; + try { + Class.forName("com.gyvex.ezafk.integration.tab.TabApiPlayerListNameAdapter"); + adapterClassAvailable = "present"; + } catch (ClassNotFoundException ex) { + adapterClassAvailable = "missing"; + } + + Registry.get().getLogger().log( + Level.FINE, + "TAB diagnostics - pluginPresent={0}, pluginEnabled={1}, apiInstance={2}, adapterClass={3}, lastFailure={4}", + new Object[]{present, enabled, apiInstance, adapterClassAvailable, lastTabAdapterFailure}); + } catch (Exception ex) { + Registry.get().getLogger().log(Level.FINE, "Failed to collect TAB diagnostics", ex); + } + } + + @Override + public void unload() { + if (tabApiAdapter != null) { + tabApiAdapter.restoreAll(); + } + bukkitAdapter.restoreAll(); + unregisterTabPlaceholder(); + } + + private void ensureTabPlaceholderRegistration() { + if (tabPlaceholderRegistered) { + return; + } + + if (!shouldUseTabPlugin()) { + return; + } + + try { + TabAPI tabAPI = TabAPI.getInstance(); + if (tabAPI == null) { + return; + } + + PlaceholderManager placeholderManager = tabAPI.getPlaceholderManager(); + if (placeholderManager == null) { + return; + } + + placeholderManager.registerPlayerPlaceholder( + TAB_AFK_PLACEHOLDER, + TAB_AFK_PLACEHOLDER_REFRESH_INTERVAL, + tabPlayer -> buildPlaceholderValue(tabPlayer)); + tabPlaceholderRegistered = true; + tabPlaceholderUnavailableLogged = false; + } catch (LinkageError ex) { + handlePlaceholderRegistrationFailure(ex); + } catch (Exception ex) { + handlePlaceholderRegistrationFailure(ex); + } + } + + private String buildPlaceholderValue(TabPlayer tabPlayer) { + if (tabPlayer == null || !tabPrefixEnabled) { + return ""; + } + + UUID uuid = tabPlayer.getUniqueId(); + if (uuid == null || !AfkState.isAfk(uuid)) { + return ""; + } + + String prefix = prefixTemplate == null ? "" : prefixTemplate; + if (prefix.isEmpty()) { + return ""; + } + return ChatColor.translateAlternateColorCodes('&', prefix); + } + + private void unregisterTabPlaceholder() { + if (!tabPlaceholderRegistered) { + return; + } + + try { + TabAPI tabAPI = TabAPI.getInstance(); + if (tabAPI != null) { + PlaceholderManager placeholderManager = tabAPI.getPlaceholderManager(); + if (placeholderManager != null) { + placeholderManager.unregisterPlaceholder(TAB_AFK_PLACEHOLDER); + } + } + } catch (LinkageError ignored) { + // Ignored: best effort cleanup. + } catch (Exception ignored) { + // Ignored: best effort cleanup. + } + tabPlaceholderRegistered = false; + } + + private void handlePlaceholderRegistrationFailure(Throwable throwable) { + if (!tabPlaceholderUnavailableLogged) { + Registry.get().getLogger().log(Level.FINE, "Failed to register TAB AFK placeholder", throwable); + tabPlaceholderUnavailableLogged = true; + } + } + + private boolean shouldUseTabPlugin() { + if (!isIntegrationEnabled()) { + return false; + } + return integrationMode != TabIntegrationMode.CUSTOM; + } + + private boolean isIntegrationEnabled() { + return Registry.get().getPlugin().getConfig().getBoolean("integration.tab"); + } + + private TabIntegrationMode resolveIntegrationMode() { + String rawValue = Registry.get().getPlugin().getConfig().getString("integration.tab-prefix.mode", "auto"); + TabIntegrationMode resolved = TabIntegrationMode.fromConfig(rawValue); + if (resolved == null) { + Registry.get().getLogger().log(Level.WARNING, "Unknown integration.tab-prefix.mode value '{0}'. Falling back to AUTO.", rawValue); + return TabIntegrationMode.AUTO; + } + return resolved; + } + + private enum TabIntegrationMode { + AUTO, + TAB, + CUSTOM; + + private static TabIntegrationMode fromConfig(String value) { + if (value == null) { + return AUTO; + } + try { + return TabIntegrationMode.valueOf(value.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + return null; + } + } + } + + private static class BukkitTabNameAdapter implements PlayerListNameAdapter { + private final Map originalPlayerListNames = new HashMap<>(); + + @Override + public void removeInvalidEntries() { + originalPlayerListNames.keySet().removeIf(uuid -> Bukkit.getPlayer(uuid) == null); + } + + @Override + public String getBaseName(Player player) { + UUID uuid = player.getUniqueId(); + return originalPlayerListNames.computeIfAbsent(uuid, id -> { + String currentListName = player.getPlayerListName(); + if (currentListName == null || currentListName.isEmpty()) { + currentListName = player.getName(); + } + return currentListName; + }); + } + + @Override + public boolean apply(Player player, String targetName) { + if (!targetName.equals(player.getPlayerListName())) { + player.setPlayerListName(targetName); + } + return true; + } + + @Override + public boolean restore(UUID uuid) { + String original = originalPlayerListNames.remove(uuid); + if (original == null) { + return false; + } + + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + player.setPlayerListName(original); + } + return true; + } + + @Override + public void restoreAll() { + Set trackedPlayers = new HashSet<>(originalPlayerListNames.keySet()); + for (UUID uuid : trackedPlayers) { + restore(uuid); + } + originalPlayerListNames.clear(); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/VoiceChatIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/VoiceChatIntegration.java new file mode 100644 index 0000000..872c6c4 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/VoiceChatIntegration.java @@ -0,0 +1,25 @@ +package com.gyvex.ezafk.integration; + +import org.bukkit.plugin.java.JavaPlugin; +import com.gyvex.ezafk.integration.voicechat.EzAfkVoicechatServiceRegistrar; + +/** + * Integration wrapper for Simple Voice Chat registration via IntegrationManager. + */ +public class VoiceChatIntegration extends Integration { + private final JavaPlugin plugin; + + public VoiceChatIntegration(JavaPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void load() { + EzAfkVoicechatServiceRegistrar.register(plugin); + } + + @Override + public void unload() { + // No specific unload actions required for Voice Chat integration + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldEditIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldEditIntegration.java new file mode 100644 index 0000000..2283df1 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldEditIntegration.java @@ -0,0 +1,136 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class WorldEditIntegration extends Integration { + // Cached helper class name kept in-memory for faster detection + private static String cachedHelperClass = null; + + @Override + public void load() { + // Only mark the integration as setup if WorldEdit is actually present + // (either by plugin manager or by WorldEdit core classes on the classpath). + EzAfk plugin = Registry.get().getPlugin(); + boolean worldEditPresent = false; + try { + // Check for WorldEdit core class + Class.forName("com.sk89q.worldedit.WorldEdit"); + worldEditPresent = true; + } catch (ClassNotFoundException ignored) { + } + try { + // Also allow detection via plugin manager if available + if (plugin != null) { + org.bukkit.plugin.Plugin wePlugin = plugin.getServer().getPluginManager().getPlugin("WorldEdit"); + if (wePlugin != null && wePlugin.isEnabled()) worldEditPresent = true; + } + } catch (Exception ignored) { + } + + if (!worldEditPresent) { + this.isSetup = false; + cachedHelperClass = null; + return; + } + + // WorldEdit is present; prefer our helper implementations if they exist. + String[] helpers = new String[]{ + "com.gyvex.ezafk.integration.worldedit.WorldEditHelper", + "com.gyvex.ezafk.integration.worldedit.LegacyWorldEditHelper" + }; + for (String cand : helpers) { + try { + Class.forName(cand); + cachedHelperClass = cand; + this.isSetup = true; + return; + } catch (ClassNotFoundException ignored) { + } + } + + // If helpers aren't present for some reason, still mark setup true so + // the fallback reflection logic in this class can attempt to read + // selections directly. + this.isSetup = true; + } + + @Override + public void unload() { + this.isSetup = false; + } + + /** + * Attempts to read a selection for the player. First tries to delegate to helper classes + * under com.gyvex.ezafk.integration.worldedit.* if available, otherwise falls back to + * the built-in reflection logic which supports older WorldEdit variants. + * Returns [min, max] Locations or null. + */ + public static Location[] getSelectionLocations(EzAfk plugin, Player p) { + // If a helper class was previously detected and cached, try it first. + if (cachedHelperClass != null) { + try { + Class helper = Class.forName(cachedHelperClass); + plugin.getLogger().fine("WorldEdit: trying cached helper " + cachedHelperClass); + java.lang.reflect.Method m = helper.getMethod("getSelectionLocations", EzAfk.class, Player.class); + Object out = m.invoke(null, plugin, p); + if (out instanceof Location[]) { + plugin.getLogger().fine("WorldEdit: cached helper returned selection"); + return (Location[]) out; + } + } catch (Exception ex) { + try { + plugin.getLogger().fine("Cached worldedit helper failed: " + ex.getMessage()); + } catch (Exception ignored) { + } + // Invalidate in-memory cache and fall back to detection + cachedHelperClass = null; + } + } + + // Delegate to helper implementations under integration.worldedit in order of preference + String[] candidates = new String[]{ + "com.gyvex.ezafk.integration.worldedit.WorldEditHelper", + "com.gyvex.ezafk.integration.worldedit.LegacyWorldEditHelper" + }; + + for (String cand : candidates) { + try { + Class helper = Class.forName(cand); + plugin.getLogger().fine("WorldEdit: trying helper " + cand); + java.lang.reflect.Method m = helper.getMethod("getSelectionLocations", EzAfk.class, Player.class); + Object out = m.invoke(null, plugin, p); + if (out instanceof Location[]) { + // cache in-memory for faster future lookups + cachedHelperClass = cand; + plugin.getLogger().fine("WorldEdit: helper " + cand + " returned selection"); + return (Location[]) out; + } + } catch (ClassNotFoundException | NoSuchMethodException ignored) { + // try next candidate + } catch (Exception ex) { + try { + plugin.getLogger().fine("integration helper " + cand + " threw: " + ex.getMessage()); + } catch (Exception ignored) { + } + } + } + + // No helper provided a selection + return null; + } + + private static Double reflectGetNumber(Object obj, String methodName) { + if (obj == null) return null; + try { + java.lang.reflect.Method m = obj.getClass().getMethod(methodName); + Object v = m.invoke(obj); + if (v instanceof Number) return ((Number) v).doubleValue(); + if (v instanceof String) return Double.parseDouble((String) v); + } catch (Exception ignored) { + } + return null; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldGuardIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldGuardIntegration.java new file mode 100644 index 0000000..650bc77 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/WorldGuardIntegration.java @@ -0,0 +1,118 @@ +package com.gyvex.ezafk.integration; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.gyvex.ezafk.integration.worldguard.flag.AfkBypassFlag; +import com.gyvex.ezafk.integration.worldguard.FlagRegistrar; +import com.sk89q.worldguard.protection.flags.StateFlag; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.Plugin; +import com.sk89q.worldguard.protection.flags.registry.FlagConflictException; + +import java.util.logging.Level; + +public class WorldGuardIntegration extends Integration { + private boolean apiAvailable = false; + private Listener pluginEnableListener = null; + + // Region checks are handled directly by callers using AfkBypassFlag.get(). + + public void setupTags() { + EzAfk plugin = Registry.get().getPlugin(); + + if (!plugin.getConfig().getBoolean("integration.worldguard")) { + return; + } + + plugin.getLogger().info("WorldGuard integration is enabled"); + + try { + boolean ok = com.gyvex.ezafk.integration.worldguard.WorldGuardSupport.registerFlagsIfPossible(plugin, plugin.getLogger()); + if (ok) { + plugin.getLogger().info("AFK BYPASS flag ready in WorldGuard"); + this.isSetup = true; + } + } catch (NoClassDefFoundError ex) { + plugin.getLogger().log( + Level.WARNING, + "WorldGuard API not available. Skipping WorldGuard integration setup.", + ex + ); + // If WorldGuard isn't present yet, listen for it being enabled so we can try again. + tryRegisterOnEnableFallback(); + } + } + + @Override + public void load() { + // Detect WorldGuard API presence at load time so callers can decide whether to proceed. + try { + Class.forName("com.sk89q.worldguard.WorldGuard"); + Class.forName("com.sk89q.worldguard.protection.flags.registry.FlagRegistry"); + Class.forName("com.sk89q.worldguard.protection.flags.StateFlag"); + this.apiAvailable = true; + } catch (ClassNotFoundException | NoClassDefFoundError ex) { + this.apiAvailable = false; + this.isSetup = false; + // If the WorldGuard API classes are not present at load time, we still + // register a listener so that if WorldGuard is later enabled we can + // attempt registration then. + tryRegisterOnEnableFallback(); + return; + } + + // If API classes are present, attempt immediate setup. + if (this.apiAvailable) { + setupTags(); + } + } + + public boolean isApiAvailable() { + return apiAvailable; + } + + @Override + public void unload() { + // Unregister any listener we created. + if (this.pluginEnableListener != null) { + try { + HandlerList.unregisterAll(this.pluginEnableListener); + } catch (Throwable ignored) { + } + this.pluginEnableListener = null; + } + } + + private void tryRegisterOnEnableFallback() { + // Avoid creating multiple listeners. + if (this.pluginEnableListener != null) return; + + EzAfk plugin = Registry.get().getPlugin(); + + this.pluginEnableListener = new Listener() { + @EventHandler + public void onPluginEnable(PluginEnableEvent event) { + if (event.getPlugin().getName().equalsIgnoreCase("WorldGuard")) { + // Attempt to setup now that WorldGuard is present. + setupTags(); + // Once attempted, unregister this listener. + try { + HandlerList.unregisterAll(this); + } catch (Throwable ignored) { + } + } + } + }; + + try { + plugin.getServer().getPluginManager().registerEvents(this.pluginEnableListener, plugin); + } catch (Throwable ex) { + plugin.getLogger().warning("Failed to register plugin-enable listener for WorldGuard fallback: " + ex.getMessage()); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/ezcountdown/EzCountdownIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/ezcountdown/EzCountdownIntegration.java new file mode 100644 index 0000000..cf77dda --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/ezcountdown/EzCountdownIntegration.java @@ -0,0 +1,300 @@ +package com.gyvex.ezafk.integration.ezcountdown; + +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.integration.Integration; +import com.skyblockexp.ezcountdown.api.EzCountdownApi; +import com.skyblockexp.ezcountdown.api.model.Notification; +import com.skyblockexp.ezcountdown.display.DisplayType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Optional integration with the EzCountdown + * plugin. + * + *

What it does

+ *

When EzCountdown is present and enabled, EzAfk delegates kick-warning + * countdowns to EzCountdown's display system instead of driving BossBar, title, + * and action-bar rendering through Bukkit reflection directly. This has several + * advantages: + *

    + *
  • EzCountdown handles cross-version display compatibility internally.
  • + *
  • The countdown ticks live (the remaining time decrements every second), + * whereas the native fallback shows a static snapshot.
  • + *
  • Two additional display types become available: + * {@code SCOREBOARD} and {@code DIALOG}, which are only supported through + * EzCountdown.
  • + *
+ * + *

Fallback

+ *

If EzCountdown is not installed, or if the integration is explicitly + * disabled, EzAfk falls back to its own Bukkit-based display code + * ({@link com.gyvex.ezafk.compatibility.player.PlayerDisplayCompat}). + * All configured display types (CHAT, TITLE, ACTION_BAR, BOSS_BAR) continue + * to work without EzCountdown. + * + *

Configuration

+ *
{@code
+ * # config.yml
+ * integration:
+ *   ezcountdown: auto   # true | false | auto (default)
+ * }
+ *
    + *
  • {@code auto} – enabled automatically when EzCountdown is detected.
  • + *
  • {@code true} – always try to enable; logs a warning if not found.
  • + *
  • {@code false} – disabled unconditionally; native display is used.
  • + *
+ * + *

Display types

+ *

When EzCountdown is active the following display types are supported in + * {@code kick.warnings.displays}: + *

+ *   CHAT        – always handled by EzAfk's MessageManager (not EzCountdown)
+ *   TITLE       – delegated to EzCountdown
+ *   ACTION_BAR  – delegated to EzCountdown
+ *   BOSS_BAR    – delegated to EzCountdown
+ *   SCOREBOARD  – EzCountdown only; ignored without EzCountdown
+ *   DIALOG      – EzCountdown only; ignored without EzCountdown
+ * 
+ * + *

Obtaining the instance

+ *
{@code
+ *   EzCountdownIntegration ez =
+ *       (EzCountdownIntegration) IntegrationManager.getIntegration("ezcountdown");
+ * }
+ */ +public class EzCountdownIntegration extends Integration { + + private EzCountdownApi api; + + /** Maps player UUID → active kick-warning countdown name. */ + private final Map activeWarnings = new HashMap<>(); + + /** + * Maps player UUID → (zone-name → active zone-reward countdown name). + * Kept separately from kick-warning countdowns so they can be managed + * independently. + */ + private final Map> activeZoneCountdowns = new HashMap<>(); + + /** + * Returns the raw {@link EzCountdownApi} instance, or {@code null} if + * EzCountdown is not available or the integration has not been loaded. + */ + public EzCountdownApi getApi() { + return api; + } + + @Override + public void load() { + try { + api = Bukkit.getServicesManager().load(EzCountdownApi.class); + isSetup = api != null; + if (isSetup) { + Registry.get().getLogger().info("EzCountdown integration enabled."); + } else { + Registry.get().getLogger() + .warning("EzCountdown plugin found but its API service is not registered. " + + "EzCountdown integration disabled."); + } + } catch (NoClassDefFoundError | Exception ex) { + api = null; + isSetup = false; + Registry.get().getLogger() + .warning("EzCountdown integration failed to load: " + ex.getMessage()); + } + } + + @Override + public void unload() { + for (UUID uuid : new ArrayList<>(activeWarnings.keySet())) { + removeKickWarning(uuid); + } + for (UUID uuid : new ArrayList<>(activeZoneCountdowns.keySet())) { + removeAllZoneCountdowns(uuid); + } + api = null; + isSetup = false; + } + + // ------------------------------------------------------------------------- + // Per-player kick-warning API + // ------------------------------------------------------------------------- + + /** + * Sends a per-player kick-warning countdown through EzCountdown's display + * system for all non-CHAT display types in {@code displays}. + * + *

The countdown targets only {@code player} via + * {@link com.skyblockexp.ezcountdown.api.model.Notification}'s + * {@code players} field, so other online players are not affected. + * + *

If a warning countdown is already active for this player it is stopped + * before the new one is created. + * + *

The {@code message} string may contain {@code %seconds%}; this is + * automatically translated to EzCountdown's live {@code {seconds}} placeholder + * so the display decrements in real time. + * + * @param player The player to warn. + * @param message Warning message template (supports {@code %seconds%} and + * EzCountdown placeholders such as {@code {seconds}}, + * {@code {formatted}}). + * @param seconds Warning duration in seconds (also the initial countdown value). + * @param displays EzAfk display type strings (TITLE, ACTION_BAR, BOSS_BAR, + * SCOREBOARD, DIALOG). {@code CHAT} entries are ignored here + * and must be sent separately via {@code MessageManager}. + * @return {@code true} if the notification was sent successfully. + */ + public boolean sendKickWarning(Player player, String message, int seconds, List displays) { + if (!isSetup || api == null || player == null || displays.isEmpty()) return false; + + UUID uuid = player.getUniqueId(); + removeKickWarning(uuid); // stop any prior warning first + + EnumSet displayTypes = mapDisplayTypes(displays); + if (displayTypes.isEmpty()) return false; + + // Translate EzAfk's %seconds% placeholder to EzCountdown's live {seconds}. + String formatMsg = message.replace("%seconds%", "{seconds}"); + + Notification notification = Notification.builder() + .duration(seconds) + .displays(displayTypes) + .message(formatMsg) + .players(List.of(player)) + .build(); + + Optional result = api.sendNotification(notification); + result.ifPresent(name -> activeWarnings.put(uuid, name)); + return result.isPresent(); + } + + /** + * Stops and removes the active EzCountdown kick-warning for the given + * player, if any. Safe to call even when no warning is active. + * + * @param playerId UUID of the player whose warning should be removed. + */ + public void removeKickWarning(UUID playerId) { + String name = activeWarnings.remove(playerId); + if (name != null) { + api.stopCountdown(name); + } + } + + /** + * Maps EzAfk display type strings to EzCountdown {@link DisplayType} values. + * + *

{@code CHAT} entries are excluded here — CHAT warnings are always sent + * through EzAfk's {@code MessageManager}, not EzCountdown. Unknown strings + * are silently ignored. + */ + private static EnumSet mapDisplayTypes(List displays) { + EnumSet result = EnumSet.noneOf(DisplayType.class); + for (String d : displays) { + try { + result.add(DisplayType.valueOf(d.toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException ignored) {} + } + result.remove(DisplayType.CHAT); + return result; + } + + // ------------------------------------------------------------------------- + // Per-player AFK-zone reward countdown API + // ------------------------------------------------------------------------- + + /** + * Starts (or restarts) a per-player countdown that shows the player how + * long until their next AFK-zone reward in {@code zoneName}. + * + *

Calling this method while a countdown is already active for the same + * player+zone will stop the old countdown first. + * + *

The {@code message} may use EzCountdown's live placeholders such as + * {@code {seconds}} and {@code {formatted}}, as well as EzAfk's own + * {@code %zone%} and {@code %amount%} which are substituted before the + * notification is sent. + * + * @param player The player to show the countdown to. + * @param zoneName Zone identifier (used as the map key for tracking). + * @param message Display message template. + * @param seconds Countdown duration in seconds. + * @param displays EzCountdown display types (TITLE, ACTION_BAR, BOSS_BAR, etc.). + * @param zonePlaceholder Value to substitute for {@code %zone%} in the message. + * @param amountPlaceholder Value to substitute for {@code %amount%} in the message. + * @return {@code true} if the notification was sent successfully. + */ + public boolean sendZoneRewardCountdown(Player player, String zoneName, String message, + int seconds, List displays, + String zonePlaceholder, String amountPlaceholder) { + if (!isSetup || api == null || player == null || seconds <= 0 || displays.isEmpty()) return false; + + UUID uuid = player.getUniqueId(); + removeZoneRewardCountdown(uuid, zoneName); // stop any prior countdown first + + EnumSet displayTypes = mapDisplayTypes(displays); + if (displayTypes.isEmpty()) return false; + + String formatMsg = message + .replace("%zone%", zonePlaceholder != null ? zonePlaceholder : "") + .replace("%amount%", amountPlaceholder != null ? amountPlaceholder : ""); + + Notification notification = Notification.builder() + .duration(seconds) + .displays(displayTypes) + .message(formatMsg) + .players(List.of(player)) + .build(); + + Optional result = api.sendNotification(notification); + if (result.isPresent()) { + activeZoneCountdowns.computeIfAbsent(uuid, k -> new HashMap<>()).put(zoneName, result.get()); + return true; + } + return false; + } + + /** + * Stops the active zone-reward countdown for the given player and zone, + * if any. Safe to call when no countdown is active. + * + * @param playerId UUID of the player. + * @param zoneName Name of the zone whose countdown should be removed. + */ + public void removeZoneRewardCountdown(UUID playerId, String zoneName) { + Map playerMap = activeZoneCountdowns.get(playerId); + if (playerMap == null) return; + String countdownName = playerMap.remove(zoneName); + if (countdownName != null && api != null) { + api.stopCountdown(countdownName); + } + if (playerMap.isEmpty()) { + activeZoneCountdowns.remove(playerId); + } + } + + /** + * Stops all active zone-reward countdowns for the given player (e.g. when + * the player quits or leaves all zones). + * + * @param playerId UUID of the player. + */ + public void removeAllZoneCountdowns(UUID playerId) { + Map playerMap = activeZoneCountdowns.remove(playerId); + if (playerMap == null || api == null) return; + for (String countdownName : playerMap.values()) { + api.stopCountdown(countdownName); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/placeholder/EzAfkPlaceholderExpansion.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/placeholder/EzAfkPlaceholderExpansion.java new file mode 100644 index 0000000..3e55fb3 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/placeholder/EzAfkPlaceholderExpansion.java @@ -0,0 +1,231 @@ +package com.gyvex.ezafk.integration.placeholder; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.manager.ZoneRewardStatsManager; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.state.LastActiveState; +import com.gyvex.ezafk.util.DurationFormatter; +import me.clip.placeholderapi.PlaceholderAPI; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.plugin.Plugin; + +import java.text.DecimalFormat; +import java.util.Locale; +import java.util.OptionalLong; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EzAfkPlaceholderExpansion extends PlaceholderExpansion { + + private static final Pattern INTEGER_PATTERN = Pattern.compile("(-?\\d+)"); + private static final DecimalFormat AMOUNT_FORMAT = new DecimalFormat("0.##"); + + private final boolean playtimeIntegrationEnabled; + private final String playtimePlaceholder; + + public EzAfkPlaceholderExpansion() { + var config = Registry.get().getPlugin().getConfig(); + this.playtimeIntegrationEnabled = config.getBoolean("integration.playtime.enabled", false); + this.playtimePlaceholder = config.getString("integration.playtime.placeholder", "%playtime_time_total_seconds%"); + } + + @Override + public String getIdentifier() { + return "ezafk"; + } + + @Override + public String getAuthor() { + return String.join(", ", Registry.get().getPlugin().getDescription().getAuthors()); + } + + @Override + public String getVersion() { + return Registry.get().getPlugin().getDescription().getVersion(); + } + + @Override + public boolean persist() { + return true; + } + + @Override + public String onRequest(OfflinePlayer offlinePlayer, String identifier) { + if (identifier == null || identifier.isEmpty()) { + return ""; + } + + String params = identifier.toLowerCase(Locale.ROOT); + + switch (params) { + case "afk_count": + case "afk_players": + return String.valueOf(AfkState.getAfkPlayerCount()); + case "active_count": + case "active_players": + return String.valueOf(AfkState.getActivePlayerCount()); + default: + return handlePlayerSpecificPlaceholder(offlinePlayer, params); + } + } + + private String handlePlayerSpecificPlaceholder(OfflinePlayer offlinePlayer, String params) { + if (offlinePlayer == null) { + return ""; + } + + UUID playerId = offlinePlayer.getUniqueId(); + + switch (params) { + case "status": return AfkState.isAfk(playerId) ? "AFK" : "ACTIVE"; + case "status_colored": return AfkState.isAfk(playerId) ? "&cAFK" : "&aACTIVE"; + case "since": return formatDurationSeconds(AfkState.getSecondsSinceAfk(playerId)); + case "last_active": return formatDurationSeconds(LastActiveState.getSecondsSinceLastActive(playerId)); + case "total_seconds": return formatDurationSeconds(AfkState.getTotalAfkSeconds(playerId)); + case "total": return formatDurationPretty(AfkState.getTotalAfkSeconds(playerId)); + case "total_formatted": return formatDurationPretty(AfkState.getTotalAfkSeconds(playerId)); + case "prefix": return getConfigValue("afk.display-name.prefix"); + case "suffix": return getConfigValue("afk.display-name.suffix"); + case "playtime_active_seconds": return formatOptionalSeconds(getActivePlaytimeSeconds(offlinePlayer, playerId), false); + case "playtime_active": return formatOptionalSeconds(getActivePlaytimeSeconds(offlinePlayer, playerId), true); + case "playtime_active_formatted": return formatOptionalSeconds(getActivePlaytimeSeconds(offlinePlayer, playerId), true); + case "zone_rewards_grants": return String.valueOf(ZoneRewardStatsManager.getTotalGrantsAllZones(playerId)); + case "zone_rewards_amount": return AMOUNT_FORMAT.format(ZoneRewardStatsManager.getTotalAmountAllZones(playerId)); + default: return handleZoneStatPlaceholder(playerId, params); + } + } + + /** + * Handles per-zone stat placeholders of the form: + *

    + *
  • {@code zone_reward__grants} – total grants in that zone
  • + *
  • {@code zone_reward__amount} – total amount earned in that zone
  • + *
+ */ + private String handleZoneStatPlaceholder(UUID playerId, String params) { + if (!params.startsWith("zone_reward_")) return ""; + String rest = params.substring("zone_reward_".length()); + if (rest.endsWith("_grants")) { + String zoneName = rest.substring(0, rest.length() - "_grants".length()); + return String.valueOf(ZoneRewardStatsManager.getStat(playerId, zoneName).totalGrants); + } + if (rest.endsWith("_amount")) { + String zoneName = rest.substring(0, rest.length() - "_amount".length()); + return AMOUNT_FORMAT.format(ZoneRewardStatsManager.getStat(playerId, zoneName).totalAmount); + } + return ""; + } + + private String getConfigValue(String path) { + String value = Registry.get().getPlugin().getConfig().getString(path, ""); + return value == null ? "" : value; + } + + private String formatDurationSeconds(long seconds) { + if (seconds < 0) { + return ""; + } + + return String.valueOf(seconds); + } + + private String formatDurationPretty(long seconds) { + if (seconds < 0) { + seconds = 0; + } + + return DurationFormatter.formatDuration(seconds); + } + + private OptionalLong getActivePlaytimeSeconds(OfflinePlayer offlinePlayer, UUID playerId) { + OptionalLong playtimeSeconds = resolvePlaytimeSeconds(offlinePlayer); + + if (playtimeSeconds.isEmpty()) { + return OptionalLong.empty(); + } + + long activeSeconds = playtimeSeconds.getAsLong() - AfkState.getTotalAfkSeconds(playerId); + + if (activeSeconds < 0) { + activeSeconds = 0; + } + + return OptionalLong.of(activeSeconds); + } + + private OptionalLong resolvePlaytimeSeconds(OfflinePlayer offlinePlayer) { + if (!isPlaytimeIntegrationActive() || offlinePlayer == null) { + return OptionalLong.empty(); + } + + if (playtimePlaceholder == null || playtimePlaceholder.isBlank()) { + return OptionalLong.empty(); + } + + String resolved = PlaceholderAPI.setPlaceholders(offlinePlayer, playtimePlaceholder); + + if (resolved == null) { + return OptionalLong.empty(); + } + + resolved = resolved.trim(); + + if (resolved.isEmpty()) { + return OptionalLong.empty(); + } + + Long parsed = parseLongValue(resolved); + + if (parsed == null) { + return OptionalLong.empty(); + } + + long seconds = parsed; + + if (seconds < 0) { + seconds = 0; + } + + return OptionalLong.of(seconds); + } + + private boolean isPlaytimeIntegrationActive() { + if (!playtimeIntegrationEnabled) { + return false; + } + + Plugin plugin = Bukkit.getPluginManager().getPlugin("Playtime"); + return plugin != null && plugin.isEnabled(); + } + + private Long parseLongValue(String value) { + try { + return Long.parseLong(value); + } catch (NumberFormatException ignored) { + } + + Matcher matcher = INTEGER_PATTERN.matcher(value); + + if (matcher.find()) { + try { + return Long.parseLong(matcher.group(1)); + } catch (NumberFormatException ignored) { + } + } + + return null; + } + + private String formatOptionalSeconds(OptionalLong seconds, boolean pretty) { + if (seconds.isEmpty()) { + return ""; + } + + long value = seconds.getAsLong(); + return pretty ? formatDurationPretty(value) : formatDurationSeconds(value); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/spigot/UpdateChecker.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/spigot/UpdateChecker.java new file mode 100644 index 0000000..147292c --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/spigot/UpdateChecker.java @@ -0,0 +1,32 @@ +package com.gyvex.ezafk.integration.spigot; + +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Scanner; +import java.util.function.Consumer; + +public class UpdateChecker { + private final JavaPlugin plugin; + private final int resourceId; + + public UpdateChecker(JavaPlugin plugin, int resourceId) { + this.plugin = plugin; + this.resourceId = resourceId; + } + + public void getVersion(final Consumer consumer) { + Registry.get().getScheduler().runTaskAsync(() -> { + try (InputStream is = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId + "/~").openStream(); Scanner scann = new Scanner(is)) { + if (scann.hasNext()) { + consumer.accept(scann.next()); + } + } catch (IOException e) { + plugin.getLogger().info("Unable to check for updates: " + e.getMessage()); + } + }); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/PlayerListNameAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/PlayerListNameAdapter.java new file mode 100644 index 0000000..34eb458 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/PlayerListNameAdapter.java @@ -0,0 +1,16 @@ +package com.gyvex.ezafk.integration.tab; + +import java.util.UUID; +import org.bukkit.entity.Player; + +public interface PlayerListNameAdapter { + void removeInvalidEntries(); + + String getBaseName(Player player); + + boolean apply(Player player, String targetName); + + boolean restore(UUID uuid); + + void restoreAll(); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/TabApiPlayerListNameAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/TabApiPlayerListNameAdapter.java new file mode 100644 index 0000000..7135245 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/tab/TabApiPlayerListNameAdapter.java @@ -0,0 +1,345 @@ +package com.gyvex.ezafk.integration.tab; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import me.neznamy.tab.api.TabAPI; +import me.neznamy.tab.api.TabPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +public class TabApiPlayerListNameAdapter implements com.gyvex.ezafk.integration.PlayerListNameAdapter { + private enum ApplyValueType { + STRING, + KYORI_COMPONENT, + BUNGEE_COMPONENT_ARRAY + } + + private static final class TargetMethods { + private final Method accessor; + private final Method apply; + private final Method reset; + private final ApplyValueType valueType; + + private TargetMethods(Method accessor, Method apply, Method reset, ApplyValueType valueType) { + this.accessor = accessor; + this.apply = apply; + this.reset = reset; + this.valueType = valueType; + } + } + + private final TabAPI tabAPI; + private final Set managedPlayers = new HashSet<>(); + private final TargetMethods targetMethods; + private final Logger logger = Bukkit.getLogger(); + + public TabApiPlayerListNameAdapter() { + TabAPI api = TabAPI.getInstance(); + if (api == null) { + throw new IllegalStateException("TAB API returned null instance"); + } + this.tabAPI = api; + this.targetMethods = resolveTargetMethods(); + } + + @Override + public void removeInvalidEntries() { + Iterator iterator = managedPlayers.iterator(); + while (iterator.hasNext()) { + UUID uuid = iterator.next(); + Player player = Bukkit.getPlayer(uuid); + TabPlayer tabPlayer = tabAPI.getPlayer(uuid); + if (player == null || tabPlayer == null) { + iterator.remove(); + } + } + } + + @Override + public String getBaseName(Player player) { + TabPlayer tabPlayer = tabAPI.getPlayer(player.getUniqueId()); + if (tabPlayer == null) { + return null; + } + + String currentListName = player.getPlayerListName(); + if (currentListName == null || currentListName.isEmpty()) { + currentListName = player.getName(); + } + return currentListName; + } + + @Override + public boolean apply(Player player, String targetName) { + TabPlayer tabPlayer = tabAPI.getPlayer(player.getUniqueId()); + if (tabPlayer == null || targetMethods.apply == null) { + return false; + } + + try { + invokeApplyMethod(tabPlayer, targetName); + managedPlayers.add(player.getUniqueId()); + return true; + } catch (ReflectiveOperationException | IllegalArgumentException | IllegalStateException ex) { + logger.log(Level.FINE, "Failed to set temporary TAB player list name", ex); + return false; + } + } + + @Override + public boolean restore(UUID uuid) { + if (!managedPlayers.remove(uuid)) { + return false; + } + + TabPlayer tabPlayer = tabAPI.getPlayer(uuid); + if (tabPlayer != null && targetMethods.reset != null) { + try { + Object invocationTarget = resolveInvocationTarget(tabPlayer); + if (invocationTarget != null) { + targetMethods.reset.invoke(invocationTarget); + } + } catch (ReflectiveOperationException | IllegalArgumentException | IllegalStateException ex) { + logger.log(Level.FINE, "Failed to reset temporary TAB player list name", ex); + } + } + return true; + } + + @Override + public void restoreAll() { + Set trackedPlayers = new HashSet<>(managedPlayers); + for (UUID uuid : trackedPlayers) { + restore(uuid); + } + managedPlayers.clear(); +} + + private TargetMethods resolveTargetMethods() { + Method applyMethod = resolveApplyMethod(TabPlayer.class); + Method resetMethod = resolveResetMethod(TabPlayer.class); + if (applyMethod != null) { + return new TargetMethods(null, applyMethod, resetMethod, determineApplyValueType(applyMethod)); + } + + Method accessor = resolveTabListAccessor(); + if (accessor != null) { + Class targetClass = accessor.getReturnType(); + Method targetApply = resolveApplyMethod(targetClass); + Method targetReset = resolveResetMethod(targetClass); + if (targetApply != null) { + return new TargetMethods(accessor, targetApply, targetReset, determineApplyValueType(targetApply)); + } + } + + return new TargetMethods(null, null, null, null); + } + + private Method resolveApplyMethod(Class clazz) { + for (Method method : clazz.getMethods()) { + if (!isPotentialApplyMethod(method)) { + continue; + } + + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != 1) { + continue; + } + + Class parameterType = parameterTypes[0]; + if (parameterType == String.class + || isKyoriComponent(parameterType) + || isBungeeComponentArray(parameterType)) { + method.setAccessible(true); + return method; + } + } + return null; + } + + private boolean isPotentialApplyMethod(Method method) { + // Looks for methods that likely apply a name value + String name = method.getName().toLowerCase(Locale.ROOT); + return name.contains("name") && (name.contains("set") || name.contains("apply")); + } + + private Method resolveResetMethod(Class clazz) { + for (Method method : clazz.getMethods()) { + String name = method.getName().toLowerCase(Locale.ROOT); + if (name.contains("reset") && name.contains("name")) { + if (method.getParameterCount() == 0) { + method.setAccessible(true); + return method; + } + } + } + return null; + } + + private Method resolveTabListAccessor() { + Class clazz = TabPlayer.class; + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != 0) { + continue; + } + + Class returnType = method.getReturnType(); + if (returnType == void.class) { + continue; + } + + String name = method.getName().toLowerCase(Locale.ROOT); + if (name.contains("tab") && name.contains("list")) { + method.setAccessible(true); + return method; + } + + if (returnType.getSimpleName().toLowerCase(Locale.ROOT).contains("tablist")) { + method.setAccessible(true); + return method; + } + } + return null; + } + + private ApplyValueType determineApplyValueType(Method method) { + if (method == null) { + return null; + } + Class parameterType = method.getParameterTypes()[0]; + if (parameterType == String.class) { + return ApplyValueType.STRING; + } + if (isKyoriComponent(parameterType)) { + return ApplyValueType.KYORI_COMPONENT; + } + if (isBungeeComponentArray(parameterType)) { + return ApplyValueType.BUNGEE_COMPONENT_ARRAY; + } + return null; + } + + private boolean isKyoriComponent(Class type) { + return KyoriComponentBridge.isComponentType(type); + } + + private boolean isBungeeComponentArray(Class type) { + return type.isArray() && BaseComponent.class.isAssignableFrom(type.getComponentType()); + } + + private void invokeApplyMethod(TabPlayer tabPlayer, String targetName) + throws InvocationTargetException, IllegalAccessException { + if (targetMethods.apply == null || targetMethods.valueType == null) { + throw new IllegalStateException("TAB temporary name method unavailable"); + } + + Object invocationTarget = resolveInvocationTarget(tabPlayer); + if (invocationTarget == null) { + throw new IllegalStateException("TAB temporary name target unavailable"); + } + + Object argument; + switch (targetMethods.valueType) { + case STRING: + argument = targetName; + break; + case KYORI_COMPONENT: + argument = KyoriComponentBridge.deserialize(targetName); + break; + case BUNGEE_COMPONENT_ARRAY: + argument = TextComponent.fromLegacyText(targetName); + break; + default: + throw new IllegalStateException("Unsupported TAB temporary name argument type"); + } + + targetMethods.apply.invoke(invocationTarget, argument); + } + + private Object resolveInvocationTarget(TabPlayer tabPlayer) + throws InvocationTargetException, IllegalAccessException { + if (targetMethods.accessor == null) { + return tabPlayer; + } + Object tabList = targetMethods.accessor.invoke(tabPlayer); + if (tabList == null) { + throw new IllegalStateException("TAB tab list accessor returned null"); + } + return tabList; + } + + private static final class KyoriComponentBridge { + private static final Class COMPONENT_CLASS = loadClass("net.kyori.adventure.text.Component"); + private static final Class COMPONENT_LIKE_CLASS = loadClass("net.kyori.adventure.text.ComponentLike"); + private static final LegacySerializer LEGACY_SERIALIZER = resolveLegacySerializer(); + + private static Class loadClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + return null; + } + } + + private static boolean isComponentType(Class type) { + if (type == null) { + return false; + } + if (COMPONENT_CLASS != null && COMPONENT_CLASS.isAssignableFrom(type)) { + return true; + } + if (COMPONENT_LIKE_CLASS != null && COMPONENT_LIKE_CLASS.isAssignableFrom(type)) { + return true; + } + return COMPONENT_CLASS != null && type.isAssignableFrom(COMPONENT_CLASS); + } + + private static Object deserialize(String value) { + if (LEGACY_SERIALIZER == null) { + throw new IllegalStateException("Kyori legacy serializer unavailable"); + } + return LEGACY_SERIALIZER.deserialize(value); + } + + private static LegacySerializer resolveLegacySerializer() { + Class serializerClass = loadClass("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer"); + if (serializerClass == null) { + return null; + } + try { + Object instance = serializerClass.getMethod("legacySection").invoke(null); + Method deserialize = serializerClass.getMethod("deserialize", String.class); + return new LegacySerializer(instance, deserialize); + } catch (ReflectiveOperationException | RuntimeException ex) { + return null; + } + } + + private static final class LegacySerializer { + private final Object instance; + private final Method deserializeMethod; + + LegacySerializer(Object instance, Method deserializeMethod) { + this.instance = instance; + this.deserializeMethod = deserializeMethod; + } + + private Object deserialize(String value) { + try { + return deserializeMethod.invoke(instance, value); + } catch (ReflectiveOperationException | RuntimeException ex) { + throw new IllegalStateException("Failed to deserialize legacy component", ex); + } + } + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatPlugin.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatPlugin.java new file mode 100644 index 0000000..9d91049 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatPlugin.java @@ -0,0 +1,28 @@ +package com.gyvex.ezafk.integration.voicechat; + +import de.maxhenkel.voicechat.api.VoicechatPlugin; +import de.maxhenkel.voicechat.api.VoicechatServerApi; +import de.maxhenkel.voicechat.api.events.EventRegistration; +import de.maxhenkel.voicechat.api.events.VoicechatServerStartedEvent; + +public class EzAfkVoicechatPlugin implements VoicechatPlugin { + private static VoicechatServerApi api; + + @Override + public void registerEvents(EventRegistration registration) { + registration.registerEvent(VoicechatServerStartedEvent.class, this::onServerStarted); + } + + @Override + public String getPluginId() { + return "ezafk"; + } + + private void onServerStarted(VoicechatServerStartedEvent event) { + api = event.getVoicechat(); + } + + public static VoicechatServerApi getApi() { + return api; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatServiceRegistrar.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatServiceRegistrar.java new file mode 100644 index 0000000..5b4f349 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatServiceRegistrar.java @@ -0,0 +1,13 @@ +package com.gyvex.ezafk.integration.voicechat; + +import de.maxhenkel.voicechat.api.BukkitVoicechatService; +import org.bukkit.plugin.java.JavaPlugin; + +public class EzAfkVoicechatServiceRegistrar { + public static void register(JavaPlugin plugin) { + BukkitVoicechatService service = plugin.getServer().getServicesManager().load(BukkitVoicechatService.class); + if (service != null) { + service.registerPlugin(new EzAfkVoicechatPlugin()); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/SimpleVoiceChatIntegration.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/SimpleVoiceChatIntegration.java new file mode 100644 index 0000000..02d4321 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/voicechat/SimpleVoiceChatIntegration.java @@ -0,0 +1,169 @@ +package com.gyvex.ezafk.integration.voicechat; + +import java.io.InputStream; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; + +import de.maxhenkel.voicechat.api.VoicechatConnection; +import de.maxhenkel.voicechat.api.VoicechatServerApi; +import de.maxhenkel.voicechat.api.audiochannel.AudioPlayer; +import de.maxhenkel.voicechat.api.audiochannel.StaticAudioChannel; + +import org.bukkit.entity.Player; + +/** + * Handles integration with Simple Voice Chat to play custom sounds for AFK events. + */ +public class SimpleVoiceChatIntegration { + private final EzAfk plugin; + + public SimpleVoiceChatIntegration(EzAfk plugin) { + this.plugin = plugin; + } + + /** + * Plays the AFK sound for the specified player using Simple Voice Chat. + * This is a stub. You must implement the actual call to Simple Voice Chat's API. + */ + public void playAfkSound(Player player) { + if (!Registry.get().getConfigManager().isAfkSoundEnabled()) return; + String soundFile = Registry.get().getConfigManager().getAfkSoundFile(); + java.io.File mp3File = new java.io.File(Registry.get().getPlugin().getDataFolder(), soundFile); // plugins/EzAfk/mp3/ezafk-sound.mp3 + if (!mp3File.exists()) { + Registry.get().getLogger().warning("AFK sound file not found: " + mp3File.getAbsolutePath()); + return; + } + java.io.InputStream mp3Stream; + try { + mp3Stream = new java.io.FileInputStream(mp3File); + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to open AFK sound file: " + e.getMessage()); + return; + } + + // Get VoicechatServerApi instance + VoicechatServerApi api = EzAfkVoicechatPlugin.getApi(); + if (api == null) return; + + // Get the VoicechatConnection for the player + VoicechatConnection connection = api.getConnectionOf(player.getUniqueId()); + if (connection == null) return; + + // Decode MP3 to 48kHz 16-bit PCM + short[] audio; + try { + audio = decodeMp3ToPcm(mp3Stream); + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to decode AFK sound: " + e.getMessage()); + return; + } + if (audio == null || audio.length == 0) return; + + // Create a StaticAudioChannel for the player using the recommended API + java.util.UUID channelId = java.util.UUID.randomUUID(); + // For Bukkit/Spigot, use api.fromServerLevel(player.getWorld()) which returns the correct type + StaticAudioChannel channel = api.createStaticAudioChannel(channelId, api.fromServerLevel(player.getWorld()), connection); + if (channel == null) return; + + // Play the audio + AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), audio); + audioPlayer.startPlaying(); + } + + /** + * Plays the return-from-AFK sound for the specified player using Simple Voice Chat. + */ + public void playReturnSound(Player player) { + if (!Registry.get().getConfigManager().isUnafkSoundEnabled()) return; + String soundFile = Registry.get().getConfigManager().getUnafkSoundFile(); + java.io.File mp3File = new java.io.File(Registry.get().getPlugin().getDataFolder(), soundFile); + if (!mp3File.exists()) { + Registry.get().getLogger().warning("Return AFK sound file not found: " + mp3File.getAbsolutePath()); + return; + } + java.io.InputStream mp3Stream; + try { + mp3Stream = new java.io.FileInputStream(mp3File); + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to open return AFK sound file: " + e.getMessage()); + return; + } + + VoicechatServerApi api = EzAfkVoicechatPlugin.getApi(); + if (api == null) return; + + VoicechatConnection connection = api.getConnectionOf(player.getUniqueId()); + if (connection == null) return; + + short[] audio; + try { + audio = decodeMp3ToPcm(mp3Stream); + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to decode return AFK sound: " + e.getMessage()); + return; + } + if (audio == null || audio.length == 0) return; + + java.util.UUID channelId = java.util.UUID.randomUUID(); + StaticAudioChannel channel = api.createStaticAudioChannel(channelId, api.fromServerLevel(player.getWorld()), connection); + if (channel == null) return; + + AudioPlayer audioPlayer = api.createAudioPlayer(channel, api.createEncoder(), audio); + audioPlayer.startPlaying(); + } + + /** + * Decodes an MP3 file to 48kHz 16-bit PCM samples (short[]). + * You may use a library like Tritonus, JLayer, or JavaSound for actual decoding. + * This is a stub and must be implemented for production use. + */ + private short[] decodeMp3ToPcm(InputStream mp3Stream) throws Exception { + // Implementation using JLayer (javazoom) + // Requires JLayer dependency: 'javazoom:jlayer:1.0.1' + try (java.io.BufferedInputStream bis = new java.io.BufferedInputStream(mp3Stream)) { + javazoom.jl.decoder.Bitstream bitstream = new javazoom.jl.decoder.Bitstream(bis); + javazoom.jl.decoder.Header header; + java.util.ArrayList pcmList = new java.util.ArrayList<>(); + javazoom.jl.decoder.Decoder decoder = new javazoom.jl.decoder.Decoder(); + while ((header = bitstream.readFrame()) != null) { + javazoom.jl.decoder.SampleBuffer output = (javazoom.jl.decoder.SampleBuffer) decoder.decodeFrame(header, bitstream); + // Resample to 48000 Hz if needed (JLayer outputs 44100 Hz by default) + short[] samples = output.getBuffer(); + int sampleRate = output.getSampleFrequency(); + if (sampleRate != 48000) { + samples = resample(samples, sampleRate, 48000); + } + for (short s : samples) { + pcmList.add(s); + } + bitstream.closeFrame(); + } + // Convert to short[] + short[] pcm = new short[pcmList.size()]; + for (int i = 0; i < pcmList.size(); i++) { + pcm[i] = pcmList.get(i); + } + return pcm; + } + } + + /** + * Resamples a PCM short[] array from srcRate to dstRate using linear interpolation. + */ + private short[] resample(short[] input, int srcRate, int dstRate) { + if (srcRate == dstRate) return input; + int srcLen = input.length; + int dstLen = (int) (((long) srcLen * dstRate) / srcRate); + short[] output = new short[dstLen]; + for (int i = 0; i < dstLen; i++) { + float srcIndex = ((float) i * srcRate) / dstRate; + int idx = (int) srcIndex; + float frac = srcIndex - idx; + short s1 = input[Math.min(idx, srcLen - 1)]; + short s2 = input[Math.min(idx + 1, srcLen - 1)]; + output[i] = (short) (s1 + frac * (s2 - s1)); + } + return output; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/LegacyWorldEditHelper.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/LegacyWorldEditHelper.java new file mode 100644 index 0000000..93b46c2 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/LegacyWorldEditHelper.java @@ -0,0 +1,88 @@ +package com.gyvex.ezafk.integration.worldedit; + +import com.gyvex.ezafk.EzAfk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; + +/** + * Legacy WorldEdit helper which implements the older Bukkit WorldEditPlugin selection reflection + * logic previously embedded in WorldEditIntegration. + */ +public class LegacyWorldEditHelper { + + public static Location[] getSelectionLocations(EzAfk plugin, Player p) { + org.bukkit.plugin.Plugin wePlugin = plugin.getServer().getPluginManager().getPlugin("WorldEdit"); + if (wePlugin == null) { + return null; + } + try { + Class weClass = Class.forName("com.sk89q.worldedit.bukkit.WorldEditPlugin"); + if (!weClass.isInstance(wePlugin)) return null; + java.lang.reflect.Method getSelection = null; + Object selection = null; + for (java.lang.reflect.Method m : weClass.getMethods()) { + if (!m.getName().equals("getSelection")) continue; + if (m.getParameterCount() != 1) continue; + try { + selection = m.invoke(wePlugin, p); + break; + } catch (IllegalArgumentException ignored) { + // try next overload + } + } + if (selection == null) return null; + Class selClass = selection.getClass(); + java.lang.reflect.Method getMinimumPoint = selClass.getMethod("getMinimumPoint"); + java.lang.reflect.Method getMaximumPoint = selClass.getMethod("getMaximumPoint"); + Object minObj = getMinimumPoint.invoke(selection); + Object maxObj = getMaximumPoint.invoke(selection); + if (minObj instanceof Location && maxObj instanceof Location) { + return new Location[]{(Location) minObj, (Location) maxObj}; + } + + Double minX = reflectGetNumber(minObj, "getX"); + Double minY = reflectGetNumber(minObj, "getY"); + Double minZ = reflectGetNumber(minObj, "getZ"); + Double maxX = reflectGetNumber(maxObj, "getX"); + Double maxY = reflectGetNumber(maxObj, "getY"); + Double maxZ = reflectGetNumber(maxObj, "getZ"); + if (minX == null || minY == null || minZ == null || maxX == null || maxY == null || maxZ == null) return null; + + String worldName = null; + try { + java.lang.reflect.Method getWorld = selClass.getMethod("getWorld"); + Object weWorld = getWorld.invoke(selection); + if (weWorld != null) { + try { + java.lang.reflect.Method getName = weWorld.getClass().getMethod("getName"); + Object nameObj = getName.invoke(weWorld); + if (nameObj != null) worldName = nameObj.toString(); + } catch (NoSuchMethodException ignored) { + } + } + } catch (NoSuchMethodException ignored) { + } + + World world = worldName != null ? org.bukkit.Bukkit.getWorld(worldName) : p.getWorld(); + if (world == null) world = p.getWorld(); + Location minLoc = new Location(world, Math.min(minX, maxX), Math.min(minY, maxY), Math.min(minZ, maxZ)); + Location maxLoc = new Location(world, Math.max(minX, maxX), Math.max(minY, maxY), Math.max(minZ, maxZ)); + return new Location[]{minLoc, maxLoc}; + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException ignored) { + return null; + } + } + + private static Double reflectGetNumber(Object obj, String methodName) { + if (obj == null) return null; + try { + java.lang.reflect.Method m = obj.getClass().getMethod(methodName); + Object v = m.invoke(obj); + if (v instanceof Number) return ((Number) v).doubleValue(); + if (v instanceof String) return Double.parseDouble((String) v); + } catch (Exception ignored) { + } + return null; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/WorldEditHelper.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/WorldEditHelper.java new file mode 100644 index 0000000..1f09421 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldedit/WorldEditHelper.java @@ -0,0 +1,138 @@ +package com.gyvex.ezafk.integration.worldedit; + +import com.gyvex.ezafk.EzAfk; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +/** + * Helper for modern WorldEdit (7+) using reflection so we avoid a hard compile dependency. + */ +public class WorldEditHelper { + + /** + * Attempts to read a WorldEdit 7+ selection for the player via reflection. + * Returns [min, max] Locations or null. + */ + public static Location[] getSelectionLocations(EzAfk plugin, Player p) { + try { + // no-op: diagnostic removed + // WorldEdit main class + Class weClass = Class.forName("com.sk89q.worldedit.WorldEdit"); + java.lang.reflect.Method getInstance = weClass.getMethod("getInstance"); + Object weInstance = getInstance.invoke(null); + + // Session manager + java.lang.reflect.Method getSessionManager = weClass.getMethod("getSessionManager"); + Object sessionManager = getSessionManager.invoke(weInstance); + + // sessionManager.get(player) -> LocalSession + Object localSession = null; + + for (java.lang.reflect.Method m : sessionManager.getClass().getMethods()) { + if (!m.getName().equals("get")) continue; + if (m.getParameterCount() != 1) continue; + Class param = m.getParameterTypes()[0]; + Object arg = null; + // If the method accepts org.bukkit.entity.Player directly + if (param.isInstance(p)) { + arg = p; + } else { + // Try adapting via BukkitAdapter.adapt(Player) if available and returns correct type + try { + Class ba = Class.forName("com.sk89q.worldedit.bukkit.BukkitAdapter"); + for (java.lang.reflect.Method am : ba.getMethods()) { + if (!am.getName().equals("adapt")) continue; + if (am.getParameterCount() != 1) continue; + if (!am.getParameterTypes()[0].isAssignableFrom(org.bukkit.entity.Player.class)) continue; + if (!param.isAssignableFrom(am.getReturnType())) continue; + try { + arg = am.invoke(null, p); + break; + } catch (Exception ignored) { + } + } + } catch (ClassNotFoundException ignored) { + } + // Try passing name or UUID if method accepts those + if (arg == null) { + if (param.isAssignableFrom(String.class)) arg = p.getName(); + else if (param.isAssignableFrom(java.util.UUID.class)) arg = p.getUniqueId(); + } + } + if (arg == null) continue; + try { + localSession = m.invoke(sessionManager, arg); + if (localSession != null) break; + } catch (IllegalArgumentException ignored) { + // try next overload + } + } + if (localSession == null) { + return null; + } + + // Adapt Bukkit world to WorldEdit world using BukkitAdapter.adapt(World) + Class bukkitAdapter = Class.forName("com.sk89q.worldedit.bukkit.BukkitAdapter"); + java.lang.reflect.Method adaptWorld = bukkitAdapter.getMethod("adapt", org.bukkit.World.class); + Object weWorld = adaptWorld.invoke(null, p.getWorld()); + + // LocalSession.getSelection(World) -> Region + Object region = null; + for (java.lang.reflect.Method m : localSession.getClass().getMethods()) { + if (!m.getName().equals("getSelection")) continue; + if (m.getParameterCount() != 1) continue; + try { + region = m.invoke(localSession, weWorld); + break; + } catch (IllegalArgumentException ignored) { + // try next overload + } + } + if (region == null) { + return null; + } + + // Region.getMinimumPoint()/getMaximumPoint() -> BlockVector3-like + java.lang.reflect.Method getMin = region.getClass().getMethod("getMinimumPoint"); + java.lang.reflect.Method getMax = region.getClass().getMethod("getMaximumPoint"); + Object minObj = getMin.invoke(region); + Object maxObj = getMax.invoke(region); + if (minObj == null || maxObj == null) return null; + + Double minX = reflectGetNumber(minObj, "getX"); + Double minY = reflectGetNumber(minObj, "getY"); + Double minZ = reflectGetNumber(minObj, "getZ"); + Double maxX = reflectGetNumber(maxObj, "getX"); + Double maxY = reflectGetNumber(maxObj, "getY"); + Double maxZ = reflectGetNumber(maxObj, "getZ"); + + if (minX == null || minY == null || minZ == null || maxX == null || maxY == null || maxZ == null) { + return null; + } + + Location minLoc = new Location(p.getWorld(), Math.min(minX, maxX), Math.min(minY, maxY), Math.min(minZ, maxZ)); + Location maxLoc = new Location(p.getWorld(), Math.max(minX, maxX), Math.max(minY, maxY), Math.max(minZ, maxZ)); + + return new Location[]{minLoc, maxLoc}; + } catch (ClassNotFoundException ex) { + // WorldEdit 7 classes not present + return null; + } catch (Exception ex) { + try { + plugin.getLogger().fine("WorldEditHelper failed: " + ex.getMessage()); + } catch (Exception ignored) {} + return null; + } + } + + private static Double reflectGetNumber(Object obj, String methodName) { + if (obj == null) return null; + try { + java.lang.reflect.Method m = obj.getClass().getMethod(methodName); + Object v = m.invoke(obj); + if (v instanceof Number) return ((Number) v).doubleValue(); + if (v instanceof String) return Double.parseDouble((String) v); + } catch (Exception ignored) {} + return null; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrar.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrar.java new file mode 100644 index 0000000..53a6d92 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrar.java @@ -0,0 +1,87 @@ +package com.gyvex.ezafk.integration.worldguard; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Reflection-based helper for registering WorldGuard flags. Uses raw Object + * parameters to avoid linking WorldGuard classes when WorldGuard is absent. + */ +public final class FlagRegistrar { + private FlagRegistrar() {} + + public static Object registerStateFlag(Object candidate, Object registry, Logger logger, String mode) { + if (candidate == null || registry == null) return null; + + try { + // registry.register(candidate); + try { + registry.getClass().getMethod("register", candidate.getClass()).invoke(registry, candidate); + } catch (NoSuchMethodException nsme) { + // try generic Object param + try { + registry.getClass().getMethod("register", Object.class).invoke(registry, candidate); + } catch (NoSuchMethodException ignored) { + // unable to call register + } + } + + // candidate.getName() + String name = tryInvokeGetName(candidate); + logger.info("Registered WorldGuard flag: " + name); + return candidate; + } catch (Throwable t) { + // handle conflict or locked registry + try { + String name = tryInvokeGetName(candidate); + Object existing = tryInvokeGet(registry, name); + if (existing != null && isInstanceOfStateFlag(existing)) { + logger.info("Using existing WorldGuard flag: " + name); + return existing; + } + + if (t instanceof IllegalStateException) { + if (mode != null && "manual".equalsIgnoreCase(mode.trim())) { + logger.info("WorldGuard flag registration configured as 'manual'; skipping automatic registration for: " + name); + return null; + } + + logger.warning("WorldGuard flag registry is locked; could not register flag: " + name); + logger.warning("Remediation: ensure EzAfk loads before WorldGuard or set integration.flag-registration: manual in config."); + return null; + } + + logger.log(Level.WARNING, "Unexpected error while registering WorldGuard flag: " + name, t); + } catch (Throwable inner) { + logger.log(Level.WARNING, "Unexpected error while registering WorldGuard flag.", t); + } + return null; + } + } + + private static String tryInvokeGetName(Object candidate) { + try { + Object res = candidate.getClass().getMethod("getName").invoke(candidate); + return res == null ? "" : res.toString(); + } catch (Throwable t) { + return ""; + } + } + + private static Object tryInvokeGet(Object registry, String name) { + try { + return registry.getClass().getMethod("get", String.class).invoke(registry, name); + } catch (Throwable t) { + return null; + } + } + + private static boolean isInstanceOfStateFlag(Object obj) { + try { + Class stateFlagClass = Class.forName("com.sk89q.worldguard.protection.flags.StateFlag"); + return stateFlagClass.isInstance(obj); + } catch (Throwable t) { + return false; + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/WorldGuardSupport.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/WorldGuardSupport.java new file mode 100644 index 0000000..d028aab --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/WorldGuardSupport.java @@ -0,0 +1,57 @@ +package com.gyvex.ezafk.integration.worldguard; + +import com.gyvex.ezafk.integration.worldguard.flag.AfkBypassFlag; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.plugin.Plugin; + +public final class WorldGuardSupport { + private WorldGuardSupport() {} + + // Track whether we've already successfully registered flags to avoid duplicate logs + private static final AtomicBoolean FLAGS_REGISTERED = new AtomicBoolean(false); + + /** + * Register all EzAfk WorldGuard flags into the provided registry (when possible). + * Returns true if at least one flag became available. + * This method is static and safe to call from `onLoad` or `onEnable` or external code. + */ + public static boolean registerFlagsIfPossible(Plugin plugin, Logger logger) { + if (plugin == null || logger == null) return false; + // If we've already registered flags successfully, skip doing it again. + if (FLAGS_REGISTERED.get()) return true; + + try { + Class worldGuardClass = Class.forName("com.sk89q.worldguard.WorldGuard"); + Object wgInstance = worldGuardClass.getMethod("getInstance").invoke(null); + Object registry = wgInstance.getClass().getMethod("getFlagRegistry").invoke(wgInstance); + + String mode = plugin.getConfig().getString("integration.flag-registration", "auto").trim().toLowerCase(); + + // Try to adopt an existing flag if present + try { + Object existing = registry.getClass().getMethod("get", String.class).invoke(registry, "afk-bypass"); + if (existing != null) { + AfkBypassFlag.set(existing); + FLAGS_REGISTERED.set(true); + return true; + } + } catch (Throwable ignored) { + // ignore and continue to attempt registration + } + + Object active = AfkBypassFlag.ensureRegistered(registry, logger, mode); + if (active != null) { + FLAGS_REGISTERED.set(true); + return true; + } + return false; + } catch (Throwable ex) { + logger.log(Level.FINE, "WorldGuard not available or registration failed: " + ex.getMessage()); + return false; + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/flag/AfkBypassFlag.java b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/flag/AfkBypassFlag.java new file mode 100644 index 0000000..e875b1d --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/integration/worldguard/flag/AfkBypassFlag.java @@ -0,0 +1,52 @@ +package com.gyvex.ezafk.integration.worldguard.flag; + +import com.gyvex.ezafk.integration.worldguard.FlagRegistrar; + +import java.util.logging.Logger; + +/** + * Holds the AFK bypass flag instance without statically referencing WorldGuard + * classes. The flag is created lazily via reflection when needed so that the + * plugin can safely load when WorldGuard is not present on the classpath. + */ +public final class AfkBypassFlag { + private static volatile Object FLAG = null; // actual type: com.sk89q.worldguard.protection.flags.StateFlag + + private AfkBypassFlag() {} + + public static Object get() { + if (FLAG == null) { + try { + Class stateFlagClass = Class.forName("com.sk89q.worldguard.protection.flags.StateFlag"); + try { + FLAG = stateFlagClass.getConstructor(String.class, boolean.class).newInstance("afk-bypass", false); + } catch (NoSuchMethodException nsme) { + // Fallback to Boolean boxed constructor if present + FLAG = stateFlagClass.getConstructor(String.class, Boolean.class).newInstance("afk-bypass", Boolean.FALSE); + } + } catch (Throwable t) { + // WorldGuard not present or failed to construct; leave FLAG null + FLAG = null; + } + } + return FLAG; + } + + public static void set(Object flag) { + if (flag != null) FLAG = flag; + } + + /** + * Ensure registered via reflection-friendly registrar. + */ + public static Object ensureRegistered(Object registry, Logger logger, String mode) { + // Ensure the FLAG instance exists (construct lazily if needed) before + // attempting registration. Previously this method used the raw + // `FLAG` field which could be null if `get()` was never called, + // causing registration to silently fail. + Object candidate = get(); + Object active = FlagRegistrar.registerStateFlag(candidate, registry, logger, mode); + if (active != null) set(active); + return active; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerActionsGUI.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerActionsGUI.java new file mode 100644 index 0000000..b4d054d --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerActionsGUI.java @@ -0,0 +1,275 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.inventory.InventoryCompat; +import com.gyvex.ezafk.compatibility.LoreUtil; +import com.gyvex.ezafk.gui.GuiAction; +import com.gyvex.ezafk.gui.GuiActionFactory; +import com.gyvex.ezafk.listener.AfkPlayerOverviewGUI.PlayerListType; +import com.gyvex.ezafk.manager.MessageManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import com.gyvex.ezafk.util.PlaceholderUtil; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class AfkPlayerActionsGUI implements Listener { + + private static final String GUI_TITLE = ChatColor.DARK_GRAY + "Player Actions"; + private static final int MIN_INVENTORY_SIZE = 9; + private static final int MAX_INVENTORY_SIZE = 54; + private static final HashMap targetPlayers = new HashMap<>(); + private static final Map returnPages = new HashMap<>(); + private static final Map returnListTypes = new HashMap<>(); + private static AfkPlayerActionsGUI instance; + + private final Map actions = new HashMap<>(); + private ItemStack backButtonItem; + private int backButtonSlot = -1; + private int inventorySize = MIN_INVENTORY_SIZE; + + public AfkPlayerActionsGUI() { + instance = this; + reloadActions(); + } + + public static void reloadConfiguredActions() { + if (instance != null) { + instance.reloadActions(); + } + } + + public static AfkPlayerActionsGUI getInstance() { + return instance; + } + + public void openGUI(Player opener, Player targetPlayer) { + openGUI(opener, targetPlayer, 1, PlayerListType.AFK); + } + + public void openGUI(Player opener, Player targetPlayer, int overviewPage) { + openGUI(opener, targetPlayer, overviewPage, PlayerListType.AFK); + } + + public void openGUI(Player opener, Player targetPlayer, int overviewPage, PlayerListType listType) { + UUID openerId = opener.getUniqueId(); + targetPlayers.put(openerId, targetPlayer != null ? targetPlayer.getUniqueId() : null); + returnPages.put(openerId, Math.max(1, overviewPage)); + returnListTypes.put(openerId, listType != null ? listType : PlayerListType.AFK); + + // Create inventory using compatibility utility for cross-version support + Inventory inventory = InventoryCompat.createInventory(null, inventorySize, GUI_TITLE); + + // Filler item for empty slots + FileConfiguration config = Registry.get().getConfigManager().getGuiConfig(); + ItemStack filler = null; + if (config.getBoolean("empty-slot-filler.enabled", true)) { + String matName = config.getString("empty-slot-filler.material", "GRAY_STAINED_GLASS_PANE"); + Material mat = Material.matchMaterial(matName); + if (mat == null) mat = Material.GRAY_STAINED_GLASS_PANE; + filler = new ItemStack(mat); + ItemMeta meta = filler.getItemMeta(); + if (meta != null) { + String display = config.getString("empty-slot-filler.display-name", " "); + List lore = config.getStringList("empty-slot-filler.lore"); + LoreUtil.setDisplayName(meta, display, null, org.bukkit.Bukkit.getLogger()); + LoreUtil.setLore(meta, lore, null, org.bukkit.Bukkit.getLogger()); + filler.setItemMeta(meta); + } + } + + // Place actions and filler + for (int i = 0; i < inventorySize; i++) { + if (actions.containsKey(i)) { + inventory.setItem(i, actions.get(i).createIcon()); + } else if (backButtonSlot == i && backButtonItem != null) { + inventory.setItem(i, backButtonItem.clone()); + } else if (filler != null) { + inventory.setItem(i, filler.clone()); + } + } + + // Open GUI for the opener + opener.openInventory(inventory); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + String title = InventoryCompat.getInventoryTitle(event); + + if (!title.equals(GUI_TITLE)) { + return; + } + + if (event.getClickedInventory() == null || event.getView().getTopInventory() != event.getClickedInventory()) { + return; + } + + event.setCancelled(true); // Prevent players from taking items from the GUI + + ItemStack clickedItem = event.getCurrentItem(); + Player player = (Player) event.getWhoClicked(); + + if (!player.hasPermission("ezafk.gui.actions") && !player.isOp()) { + MessageManager.sendMessage(player, "gui.actions.no-permission", + "&cYou don't have permission to use player actions."); + player.closeInventory(); + return; + } + + if (clickedItem == null || clickedItem.getType() == Material.AIR) { + return; + } + + if (backButtonSlot >= 0 && event.getSlot() == backButtonSlot) { + handleBackButtonClick(player); + return; + } + + GuiAction action = actions.get(event.getSlot()); + + if (action == null) { + return; + } + + UUID targetPlayerId = targetPlayers.get(player.getUniqueId()); + Player targetPlayer = targetPlayerId != null ? Bukkit.getPlayer(targetPlayerId) : null; + + action.execute(player, targetPlayer); + UUID playerId = player.getUniqueId(); + targetPlayers.remove(playerId); + returnPages.remove(playerId); + returnListTypes.remove(playerId); + } + + private void reloadActions() { + actions.clear(); + inventorySize = MIN_INVENTORY_SIZE; + backButtonSlot = -1; + backButtonItem = createBackButtonItem(); + + FileConfiguration config = Registry.get().getConfigManager().getGuiConfig(); + ConfigurationSection section = config.getConfigurationSection("actions"); + + int configuredSize = config.getInt("inventory-size", config.getInt("actions.inventory-size", MIN_INVENTORY_SIZE)); + inventorySize = normalizeInventorySize(configuredSize); + + if (section == null) { + Registry.get().getLogger().warning("No GUI actions configured. The player actions GUI will be empty."); + } else { + for (String key : section.getKeys(false)) { + ConfigurationSection actionSection = section.getConfigurationSection(key); + if (actionSection == null) { + continue; + } + int slot = actionSection.getInt("slot", actions.size()); + if (slot < 0) { + continue; + } + if (slot >= MAX_INVENTORY_SIZE) { + Registry.get().getLogger().warning("Slot " + slot + " for GUI action '" + key + "' exceeds the maximum supported size. Skipping..."); + continue; + } + GuiAction action = GuiActionFactory.fromConfigSection(actionSection); + actions.put(slot, action); + int requiredSize = ((slot / 9) + 1) * 9; + if (requiredSize > MAX_INVENTORY_SIZE) { + requiredSize = MAX_INVENTORY_SIZE; + } + inventorySize = Math.max(inventorySize, requiredSize); + } + } + ensureBackButtonSlot(); + } + + private void handleBackButtonClick(Player player) { + UUID playerId = player.getUniqueId(); + int page = returnPages.getOrDefault(playerId, 1); + PlayerListType listType = returnListTypes.getOrDefault(playerId, PlayerListType.AFK); + + targetPlayers.remove(playerId); + returnPages.remove(playerId); + returnListTypes.remove(playerId); + + com.gyvex.ezafk.listener.AfkPlayerOverviewGUI overviewGUI = new com.gyvex.ezafk.listener.AfkPlayerOverviewGUI(); + overviewGUI.openGUI(player, page, listType); + } + + private void ensureBackButtonSlot() { + if (backButtonItem == null) { + return; + } + + backButtonSlot = findAvailableBackSlot(); + + while (backButtonSlot < 0 && inventorySize < MAX_INVENTORY_SIZE) { + inventorySize += 9; + backButtonSlot = findAvailableBackSlot(); + } + + if (backButtonSlot < 0) { + Registry.get().getLogger().warning("Failed to allocate a slot for the back button in the player actions GUI."); + } + } + + private int findAvailableBackSlot() { + for (int slot = inventorySize - 1; slot >= 0; slot--) { + if (!actions.containsKey(slot)) { + return slot; + } + } + + return -1; + } + + private int normalizeInventorySize(int configuredSize) { + int size = configuredSize; + + if (size < MIN_INVENTORY_SIZE) { + size = MIN_INVENTORY_SIZE; + } else if (size > MAX_INVENTORY_SIZE) { + size = MAX_INVENTORY_SIZE; + } + + if (size % 9 != 0) { + size = ((size + 8) / 9) * 9; + } + + if (size != configuredSize) { + Registry.get().getLogger().warning("Adjusted player actions GUI inventory size from " + configuredSize + " to " + size + " (must be a multiple of 9 between 9 and 54)."); + } + + return size; + } + + private ItemStack createBackButtonItem() { + FileConfiguration config = Registry.get().getConfigManager().getGuiConfig(); + ItemStack item = new ItemStack(Material.ARROW); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + String display = config.getString("back-button.display-name", "&6« Back to Overview"); + List lore = config.getStringList("back-button.lore"); + if (lore == null || lore.isEmpty()) { + lore = java.util.Collections.singletonList("&7Return to the player list."); + } + LoreUtil.setDisplayName(meta, display, null, org.bukkit.Bukkit.getLogger()); + LoreUtil.setLore(meta, lore, null, org.bukkit.Bukkit.getLogger()); + item.setItemMeta(meta); + } + return item; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerOverviewGUI.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerOverviewGUI.java new file mode 100644 index 0000000..628b23f --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/AfkPlayerOverviewGUI.java @@ -0,0 +1,354 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.inventory.InventoryCompat; +import com.gyvex.ezafk.compatibility.item.HeadCompat; +import com.gyvex.ezafk.compatibility.item.ItemMetaCompat; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.state.AfkStatusDetails; +import com.gyvex.ezafk.state.LastActiveState; +import com.gyvex.ezafk.util.DurationFormatter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class AfkPlayerOverviewGUI implements Listener { + + private static final DateTimeFormatter LAST_SEEN_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + .withZone(ZoneId.systemDefault()); + private static final int INVENTORY_SIZE = 54; + private static final int PAGE_SIZE = 45; + private static final int PREVIOUS_PAGE_SLOT = 45; + private static final int NEXT_PAGE_SLOT = 53; + private static final int LIST_TOGGLE_SLOT = 49; + + public enum PlayerListType { + AFK("AFK Players"), + ACTIVE("Active Players"); + + private final String friendlyName; + + PlayerListType(String friendlyName) { + this.friendlyName = friendlyName; + } + + public String getTitle() { + return ChatColor.DARK_GRAY + friendlyName; + } + + public String getFriendlyName() { + return friendlyName; + } + + public PlayerListType getOpposite() { + return this == AFK ? ACTIVE : AFK; + } + + public String getToggleDisplayName() { + return ChatColor.AQUA + "View " + getOpposite().getFriendlyName(); + } + + public static PlayerListType fromTitle(String title) { + for (PlayerListType type : values()) { + if (title.startsWith(type.getTitle())) { + return type; + } + } + return null; + } + } + + private static final String PLAYER_UUID_KEY = "afk_overview_uuid"; + private static final String LIST_TOGGLE_KEY = "afk_overview_list_toggle"; + + public AfkPlayerOverviewGUI() { + } + + public void openGUI(Player player, int page) { + openGUI(player, page, PlayerListType.AFK); + } + + public void openGUI(Player player, int page, PlayerListType listType) { + List trackedPlayers = getPlayers(listType); + + int totalPages = Math.max(1, (int) Math.ceil((double) trackedPlayers.size() / PAGE_SIZE)); + + if (page < 1) { + page = 1; + } else if (page > totalPages) { + page = totalPages; + } + + Inventory inventory = Bukkit.createInventory(null, INVENTORY_SIZE, listType.getTitle() + " - Page " + page); + + if (!trackedPlayers.isEmpty()) { + int startIndex = (page - 1) * PAGE_SIZE; + int endIndex = Math.min(startIndex + PAGE_SIZE, trackedPlayers.size()); + + for (int i = startIndex; i < endIndex; i++) { + OfflinePlayer offlinePlayer = trackedPlayers.get(i); + ItemStack playerHead = createPlayerHead(offlinePlayer); + inventory.addItem(playerHead); + } + } + + ItemStack prevPageItem = createButtonItem(ChatColor.GREEN + "Previous Page", Material.ARROW); + ItemStack nextPageItem = createButtonItem(ChatColor.GREEN + "Next Page", Material.ARROW); + + if (page > 1) { + inventory.setItem(PREVIOUS_PAGE_SLOT, prevPageItem); + } + if (page < totalPages) { + inventory.setItem(NEXT_PAGE_SLOT, nextPageItem); + } + + inventory.setItem(LIST_TOGGLE_SLOT, createToggleItem(listType)); + + player.openInventory(inventory); + } + + private List getPlayers(PlayerListType listType) { + List players = new ArrayList<>(); + + if (listType == PlayerListType.AFK) { + List afkPlayers = new ArrayList<>(AfkState.afkPlayers); + for (UUID playerId : afkPlayers) { + players.add(Bukkit.getOfflinePlayer(playerId)); + } + } else { + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + if (!AfkState.isAfk(onlinePlayer.getUniqueId())) { + players.add(onlinePlayer); + } + } + } + + return players; + } + + private ItemStack createPlayerHead(OfflinePlayer offlinePlayer) { + ItemStack head = HeadCompat.createPlayerHead(); + ItemMeta meta = head.getItemMeta(); + + if (!(meta instanceof SkullMeta)) { + return head; + } + + SkullMeta skullMeta = (SkullMeta) meta; + HeadCompat.setSkullOwner(skullMeta, offlinePlayer); + + String displayName = offlinePlayer.getName(); + if (displayName == null || displayName.isEmpty()) { + displayName = offlinePlayer.getUniqueId().toString(); + } + skullMeta.setDisplayName(ChatColor.YELLOW + displayName); + + List lore = buildPlayerLore(offlinePlayer); + if (!lore.isEmpty()) { + skullMeta.setLore(lore); + } + + ItemMetaCompat.setItemMetadata(skullMeta, PLAYER_UUID_KEY, offlinePlayer.getUniqueId().toString()); + + head.setItemMeta(skullMeta); + return head; + } + + private List buildPlayerLore(OfflinePlayer offlinePlayer) { + List lore = new ArrayList<>(); + UUID playerId = offlinePlayer.getUniqueId(); + + if (offlinePlayer.isOnline()) { + lore.add(ChatColor.GRAY + "Status: " + ChatColor.GREEN + "Online"); + Player onlinePlayer = offlinePlayer.getPlayer(); + if (onlinePlayer != null) { + lore.add(ChatColor.GRAY + "World: " + ChatColor.AQUA + onlinePlayer.getWorld().getName()); + } + long lastActive = LastActiveState.getSecondsSinceLastActive(playerId); + lore.add(ChatColor.GRAY + "Last Activity: " + + ChatColor.AQUA + DurationFormatter.formatDuration(lastActive)); + } else { + lore.add(ChatColor.GRAY + "Status: " + ChatColor.RED + "Offline"); + long lastPlayed = offlinePlayer.getLastPlayed(); + if (lastPlayed > 0L) { + Duration since = Duration.between(Instant.ofEpochMilli(lastPlayed), Instant.now()); + if (since.isNegative()) { + since = Duration.ZERO; + } + + lore.add(ChatColor.GRAY + "Last Active: " + ChatColor.AQUA + + DurationFormatter.formatDuration(since.getSeconds()) + ChatColor.GRAY + " ago"); + lore.add(ChatColor.GRAY + "Last Seen: " + ChatColor.AQUA + LAST_SEEN_FORMATTER.format(Instant.ofEpochMilli(lastPlayed))); + } + } + + if (AfkState.isAfk(playerId)) { + long afkSeconds = AfkState.getSecondsSinceAfk(playerId); + if (afkSeconds >= 0) { + lore.add(ChatColor.GRAY + "AFK for: " + ChatColor.AQUA + + DurationFormatter.formatDuration(afkSeconds)); + } + AfkStatusDetails details = AfkState.getAfkStatusDetails(playerId); + if (details != null) { + lore.add(ChatColor.GRAY + "Reason: " + ChatColor.YELLOW + details.getReasonDisplayName()); + if (details.hasDetail()) { + lore.add(ChatColor.DARK_GRAY + details.detail()); + } + } + } + + return lore; + } + + private ItemStack createButtonItem(String displayName, Material material) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(displayName); + item.setItemMeta(meta); + } + return item; + } + + private ItemStack createToggleItem(PlayerListType currentType) { + ItemStack item = new ItemStack(Material.NETHER_STAR); + ItemMeta meta = item.getItemMeta(); + + if (meta != null) { + meta.setDisplayName(currentType.getToggleDisplayName()); + + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "Switch to " + ChatColor.AQUA + currentType.getOpposite().getFriendlyName()); + meta.setLore(lore); + + ItemMetaCompat.setItemMetadata(meta, LIST_TOGGLE_KEY, currentType.getOpposite().name()); + + item.setItemMeta(meta); + } + + return item; + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + String title = InventoryCompat.getInventoryTitle(event); + + PlayerListType listType = PlayerListType.fromTitle(title); + + if (listType == null) { + return; + } + + if (event.getClickedInventory() == null || event.getView().getTopInventory() != event.getClickedInventory()) { + return; + } + + event.setCancelled(true); // Prevent players from taking items from the GUI + + ItemStack clickedItem = event.getCurrentItem(); + Player player = (Player) event.getWhoClicked(); + int currentPage = 1; + + int lastSpace = title.lastIndexOf(" "); + if (lastSpace >= 0) { + try { + currentPage = Integer.parseInt(title.substring(lastSpace + 1)); + } catch (NumberFormatException ignored) { + currentPage = 1; + } + } + + if (clickedItem == null || clickedItem.getType() == Material.AIR) { + return; + } + + ItemMeta itemMeta = clickedItem.getItemMeta(); + + if (itemMeta != null) { + String targetListTypeName = ItemMetaCompat.getItemMetadata(itemMeta, LIST_TOGGLE_KEY); + + if (targetListTypeName != null) { + try { + PlayerListType targetType = PlayerListType.valueOf(targetListTypeName); + if (targetType == PlayerListType.ACTIVE + && !player.hasPermission("ezafk.gui.view-active") + && !player.isOp()) { + MessageManager.sendMessage(player, "gui.overview.active.no-permission", + "&cYou don't have permission to view active players."); + return; + } + openGUI(player, 1, targetType); + } catch (IllegalArgumentException exception) { + Registry.get().getLogger().warning("Invalid overview list type metadata: " + targetListTypeName); + } + return; + } + } + + if (HeadCompat.isPlayerHead(clickedItem)) { + if (!player.hasPermission("ezafk.gui.actions") && !player.isOp()) { + MessageManager.sendMessage(player, "gui.actions.no-permission", + "&cYou don't have permission to use player actions."); + return; + } + if (itemMeta == null) { + return; + } + + String storedUuid = ItemMetaCompat.getItemMetadata(itemMeta, PLAYER_UUID_KEY); + + if (storedUuid == null) { + Registry.get().getLogger().warning("Player head missing AFK UUID metadata"); + return; + } + + UUID playerId; + try { + playerId = UUID.fromString(storedUuid); + } catch (IllegalArgumentException exception) { + Registry.get().getLogger().warning("Invalid AFK UUID metadata: " + storedUuid); + return; + } + + com.gyvex.ezafk.listener.AfkPlayerActionsGUI actionsGUI = com.gyvex.ezafk.listener.AfkPlayerActionsGUI.getInstance(); + if (actionsGUI == null) { + Registry.get().getLogger().warning("Player actions GUI is not initialized. Unable to open player actions view."); + return; + } + + actionsGUI.openGUI(player, Bukkit.getPlayer(playerId), currentPage, listType); + } else if (clickedItem.getType() == Material.ARROW) { + // Handle pagination buttons + if (itemMeta == null) { + return; + } + + String displayName = itemMeta.getDisplayName(); + + if ((ChatColor.GREEN + "Previous Page").equals(displayName)) { + openGUI(player, currentPage - 1, listType); + } else if ((ChatColor.GREEN + "Next Page").equals(displayName)) { + openGUI(player, currentPage + 1, listType); + } + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/EconomyServiceListener.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/EconomyServiceListener.java new file mode 100644 index 0000000..6677655 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/EconomyServiceListener.java @@ -0,0 +1,83 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.manager.IntegrationManager; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServiceRegisterEvent; +import org.bukkit.event.server.ServiceUnregisterEvent; +import org.bukkit.plugin.RegisteredServiceProvider; + +import java.util.logging.Logger; + +public class EconomyServiceListener implements Listener { + + @EventHandler + public void onServiceRegister(ServiceRegisterEvent event) { + if (!isEconomyService(event.getProvider())) { + return; + } + + com.gyvex.ezafk.integration.EconomyIntegration economyIntegration = getEconomyIntegration(); + + if (economyIntegration == null) { + return; + } + + economyIntegration.load(); + + if (economyIntegration.isSetup) { + log("Economy provider registered; refreshed economy integration."); + } + } + + @EventHandler + public void onServiceUnregister(ServiceUnregisterEvent event) { + if (!isEconomyService(event.getProvider())) { + return; + } + + com.gyvex.ezafk.integration.EconomyIntegration economyIntegration = getEconomyIntegration(); + + if (economyIntegration == null) { + return; + } + + economyIntegration.unload(); + log("Economy provider unregistered; unloaded economy integration."); + } + + private boolean isEconomyService(RegisteredServiceProvider provider) { + try { + return provider != null && Economy.class.equals(provider.getService()); + } catch (NoClassDefFoundError e) { + return false; + } + } + + private com.gyvex.ezafk.integration.EconomyIntegration getEconomyIntegration() { + com.gyvex.ezafk.integration.Integration integration = IntegrationManager.getIntegration("economy"); + + if (integration instanceof com.gyvex.ezafk.integration.EconomyIntegration) { + return (com.gyvex.ezafk.integration.EconomyIntegration) integration; + } + + return null; + } + + private void log(String message) { + EzAfk plugin = Registry.get().getPlugin(); + + if (plugin == null) { + return; + } + + Logger logger = plugin.getLogger(); + + if (logger != null) { + logger.info(message); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/MoveListener.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/MoveListener.java new file mode 100644 index 0000000..25e4fff --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/MoveListener.java @@ -0,0 +1,246 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.manager.AfkZoneManager; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.state.AfkActivationMode; +import com.gyvex.ezafk.state.AfkReason; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.state.LastActiveState; +import com.gyvex.ezafk.zone.Zone; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Levelled; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class MoveListener implements Listener { + private final EzAfk plugin; + private final Set vehicleBypassHandled = new HashSet<>(); + private final Set infiniteWaterFlowHandled = new HashSet<>(); + private final Set bubbleColumnHandled = new HashSet<>(); + /** Tracks the AFK zone name each player was in after their last position change. */ + private static final Map playerZoneCache = new HashMap<>(); + + public MoveListener(EzAfk plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + UUID playerId = player.getUniqueId(); + + boolean antiVehicle = this.plugin.getConfig().getBoolean("afk.anti.infinite-vehicle"); + boolean flagOnly = this.plugin.getConfig().getBoolean("afk.anti.flag-only"); + + if (antiVehicle && player.isInsideVehicle()) { + if (flagOnly) { + if (vehicleBypassHandled.add(playerId)) { + markBypass(player, AfkReason.ANTI_VEHICLE, + "Vehicle movement detected at " + formatLocation(player.getLocation())); + } + } else { + if (vehicleBypassHandled.add(playerId)) { + MessageManager.sendMessage(player, "anti.vehicle", + "&cYou cannot AFK while inside a vehicle."); + } + if (player.isInsideVehicle()) { + player.leaveVehicle(); + } + } + return; + } + + boolean antiInfiniteWaterFlow = this.plugin.getConfig().getBoolean("afk.anti.infinite-waterflow"); + + if (antiInfiniteWaterFlow && isInInfiniteWaterFlow(player)) { + if (flagOnly) { + if (infiniteWaterFlowHandled.add(playerId)) { + markBypass(player, AfkReason.ANTI_INFINITE_WATER, + "Water flow detected at " + formatLocation(player.getLocation())); + } + } else { + if (infiniteWaterFlowHandled.add(playerId)) { + MessageManager.sendMessage(player, "anti.infinite-waterflow", + "&cYou cannot use water flow to avoid AFK detection."); + } + } + return; + } + + boolean antiBubbleColumn = this.plugin.getConfig().getBoolean("afk.anti.bubble-column"); + + if (antiBubbleColumn && isInBubbleColumn(player)) { + if (flagOnly) { + if (bubbleColumnHandled.add(playerId)) { + markBypass(player, AfkReason.ANTI_BUBBLE_COLUMN, + "Bubble column detected at " + formatLocation(player.getLocation())); + } + } else { + if (bubbleColumnHandled.add(playerId)) { + MessageManager.sendMessage(player, "anti.bubble-column", + "&cYou cannot use bubble columns to avoid AFK detection."); + } + } + return; + } + + if (!hasMeaningfulPositionChange(event)) { + // Ignore orientation-only updates so AFK detection only reacts to real movement. + return; + } + + LastActiveState.update(player); + checkZoneTransition(player); + vehicleBypassHandled.remove(playerId); + infiniteWaterFlowHandled.remove(playerId); + + if (AfkState.isAfk(playerId)) { + AfkState.disable(this.plugin, player); + } + } + + private void checkZoneTransition(Player player) { + Zone current = AfkZoneManager.getZoneForPlayer(player); + String currentName = current == null ? null : current.name; + UUID playerId = player.getUniqueId(); + String previousName = playerZoneCache.get(playerId); + + if (currentName == null && previousName == null) return; + if (currentName != null && currentName.equals(previousName)) return; + + if (currentName != null) { + // Entered a zone (or moved from one zone directly into another) + Map ph = new HashMap<>(); + ph.put("zone", currentName); + MessageManager.sendMessage(player, "afkzone.enter", + "&aYou have entered the AFK zone &e%zone%&a.", ph); + playerZoneCache.put(playerId, currentName); + } else { + // Exited a zone + Map ph = new HashMap<>(); + ph.put("zone", previousName); + MessageManager.sendMessage(player, "afkzone.exit", + "&7You have left the AFK zone &e%zone%&7.", ph); + playerZoneCache.remove(playerId); + } + } + + /** Called by {@link PlayerQuitListener} to clean up cached zone state. */ + public static void clearZoneState(UUID playerId) { + playerZoneCache.remove(playerId); + } + + private void markBypass(Player player, AfkReason reason, String detail) { + AfkState.markAfk(this.plugin, player, reason, detail, AfkActivationMode.SILENT); + } + + private String formatLocation(Location location) { + if (location.getWorld() == null) { + return String.format("(%.1f, %.1f, %.1f)", + location.getX(), location.getY(), location.getZ()); + } + + return String.format("%s (%.1f, %.1f, %.1f)", + location.getWorld().getName(), + location.getX(), + location.getY(), + location.getZ()); + } + + private boolean hasMeaningfulPositionChange(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + + if (to == null) { + return false; + } + + if (from.getWorld() != to.getWorld()) { + return true; + } + + double dx = to.getX() - from.getX(); + double dy = to.getY() - from.getY(); + double dz = to.getZ() - from.getZ(); + + double distanceSquared = dx * dx + dy * dy + dz * dz; + + return distanceSquared > 1.0E-4; + } + + private boolean isInInfiniteWaterFlow(Player player) { + Block feetBlock = player.getLocation().getBlock(); + Block bodyBlock = feetBlock.getRelative(BlockFace.UP); + + return isFlowingWaterThatPushes(feetBlock) || isFlowingWaterThatPushes(bodyBlock); + } + + private boolean isFlowingWaterThatPushes(Block block) { + if (block.getType() != Material.WATER) { + return false; + } + + Block below = block.getRelative(BlockFace.DOWN); + if (below.getType() == Material.BUBBLE_COLUMN) { + return false; + } + + BlockData blockData = block.getBlockData(); + if (!(blockData instanceof Levelled)) { + return false; + } + Levelled levelled = (Levelled) blockData; + + int level = levelled.getLevel(); + + if (level == 0) { + // Still source water; normal swimming is allowed. + return false; + } + + // Falling water (maximum level) should only flag if there is space to fall into. + if (level >= levelled.getMaximumLevel()) { + return below.isPassable(); + } + + for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}) { + Block relative = block.getRelative(face); + + if (relative.isPassable() && relative.getType() != Material.WATER) { + return true; + } + + if (relative.getType() == Material.WATER && relative.getBlockData() instanceof Levelled) { + Levelled adjacentLevelled = (Levelled) relative.getBlockData(); + if (adjacentLevelled.getLevel() > level) { + return true; + } + } + } + + return false; + } + + boolean isInBubbleColumn(Player player) { + Block feet = player.getLocation().getBlock(); + Block body = feet.getRelative(BlockFace.UP); + Block below = feet.getRelative(BlockFace.DOWN); + + return feet.getType() == Material.BUBBLE_COLUMN + || body.getType() == Material.BUBBLE_COLUMN + || below.getType() == Material.BUBBLE_COLUMN; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerActivityListener.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerActivityListener.java new file mode 100644 index 0000000..927a0a8 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerActivityListener.java @@ -0,0 +1,48 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.state.LastActiveState; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class PlayerActivityListener implements Listener { + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + LastActiveState.update(event.getPlayer()); + } + + @SuppressWarnings("deprecation") // AsyncPlayerChatEvent deprecated in Paper 1.19+; still fires on all supported versions + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent event) { + LastActiveState.update(event.getPlayer()); + } + + @EventHandler + public void onPlayerCommand(PlayerCommandPreprocessEvent event) { + LastActiveState.update(event.getPlayer()); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + HumanEntity human = event.getWhoClicked(); + + if (!(human instanceof Player)) { + return; + } + Player player = (Player) human; + + if (event.getClickedInventory() == null) { + return; + } + + if (event.getView().getTopInventory().equals(event.getClickedInventory())) { + LastActiveState.update(player); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerQuitListener.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerQuitListener.java new file mode 100644 index 0000000..5cda32b --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/PlayerQuitListener.java @@ -0,0 +1,43 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.manager.AfkZoneRewardManager; +import com.gyvex.ezafk.manager.EconomyManager; +import com.gyvex.ezafk.manager.AfkTimeManager; +import com.gyvex.ezafk.manager.ZoneRewardStatsManager; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.zone.ZoneCache; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public class PlayerQuitListener implements Listener { + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + + UUID playerId = player.getUniqueId(); + AfkTimeManager.flushActiveSessions(java.util.Map.of(playerId, System.currentTimeMillis())); + + // Ensure any stored zone positions are cleared when the player quits + ZoneCache.clearPositions(playerId); + MoveListener.clearZoneState(playerId); + + // Clean up zone reward countdowns and session stats + AfkZoneRewardManager.stopAllZoneCountdowns(playerId); + ZoneRewardStatsManager.clearPlayer(playerId); + + if (!AfkState.afkPlayers.contains(playerId)) { + AfkState.forgetDisplayName(playerId); + EconomyManager.onDisable(playerId); + return; + } + + AfkState.afkPlayers.remove(playerId); + AfkState.forgetDisplayName(playerId); + EconomyManager.onDisable(playerId); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/listener/SimpleVoiceChatAfkListener.java b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/SimpleVoiceChatAfkListener.java new file mode 100644 index 0000000..e48472e --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/listener/SimpleVoiceChatAfkListener.java @@ -0,0 +1,26 @@ +package com.gyvex.ezafk.listener; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.event.PlayerAfkStatusChangeEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +/** + * Listens for AFK status changes and triggers Simple Voice Chat sound playback. + */ +public class SimpleVoiceChatAfkListener implements Listener { + private final com.gyvex.ezafk.integration.voicechat.SimpleVoiceChatIntegration svcIntegration; + + public SimpleVoiceChatAfkListener(EzAfk plugin) { + this.svcIntegration = new com.gyvex.ezafk.integration.voicechat.SimpleVoiceChatIntegration(plugin); + } + + @EventHandler + public void onAfkStatusChange(PlayerAfkStatusChangeEvent event) { + if (event.isAfk()) { + svcIntegration.playAfkSound(event.getPlayer()); + } else { + svcIntegration.playReturnSound(event.getPlayer()); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkTimeManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkTimeManager.java new file mode 100644 index 0000000..1a35b14 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkTimeManager.java @@ -0,0 +1,437 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import com.gyvex.ezafk.compatibility.scheduler.TaskHandle; + +public final class AfkTimeManager { + private static final Map totalAfkSeconds = new ConcurrentHashMap<>(); + private static final Set dirtyPlayers = ConcurrentHashMap.newKeySet(); + private static final Map pendingSaves = new ConcurrentHashMap<>(); + private static final int LEADERBOARD_CACHE_SIZE = 100; + private static final NavigableSet leaderboardCache = new TreeSet<>((first, second) -> { + int comparison = Long.compare(second.totalSeconds, first.totalSeconds); + if (comparison != 0) { + return comparison; + } + return first.playerId.compareTo(second.playerId); + }); + private static final Map leaderboardEntries = new HashMap<>(); + + private static TaskHandle flushTask; + private static long flushIntervalTicks = 20L * 30L; + private static final long DEFAULT_FLUSH_INTERVAL_SECONDS = 30L; + private static java.io.File timesDirectory = null; + private AfkTimeManager() { + } + + public static void load(EzAfk plugin) { + if (plugin == null) { + return; + } + + File dataFolder = plugin.getDataFolder(); + if (!dataFolder.exists() && !dataFolder.mkdirs()) { + plugin.getLogger().warning("Unable to create EzAfk data folder for AFK time storage."); + return; + } + + stopFlushTask(); + + totalAfkSeconds.clear(); + dirtyPlayers.clear(); + pendingSaves.clear(); + clearLeaderboardCache(); + + migrateLegacyData(plugin, new File(dataFolder, "afk-times.yml")); + + // Load from configured storage repository if available (preferred) + if (Registry.get().getStorageRepository() != null) { + try { + java.util.Map stored = Registry.get().getStorageRepository().loadAll(); + if (stored != null) { + totalAfkSeconds.putAll(stored); + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to load AFK times from storage repository: " + e.getMessage()); + } + } else { + plugin.getLogger().warning("No storage repository configured; AFK times will be kept in-memory only."); + } + + rebuildLeaderboardCache(); + + configureFlushTask(plugin); + } + + + + private static void migrateLegacyData(EzAfk plugin, File legacyFile) { + if (legacyFile == null || !legacyFile.exists()) { + return; + } + + FileConfiguration configuration = YamlConfiguration.loadConfiguration(legacyFile); + if (configuration.isConfigurationSection("totals")) { + for (String key : configuration.getConfigurationSection("totals").getKeys(false)) { + try { + UUID uuid = UUID.fromString(key); + long seconds = configuration.getLong("totals." + key, 0L); + if (seconds > 0L) { + totalAfkSeconds.put(uuid, seconds); + if (!persistPlayer(uuid, seconds, plugin)) { + queueSave(uuid, seconds); + } + } + } catch (IllegalArgumentException exception) { + plugin.getLogger().warning("Invalid UUID found in afk-times.yml: " + key); + } + } + } + + if (!legacyFile.delete()) { + plugin.getLogger().warning("Unable to delete legacy afk-times.yml after migration."); + } + } + + public static void saveAll() { + if (timesDirectory == null) { + return; + } + + EzAfk plugin = Registry.get().getPlugin(); + if (plugin == null) { + return; + } + + // Persist via storage repository when available + if (Registry.get().getStorageRepository() != null) { + try { + flushPending(plugin, true); + Registry.get().getStorageRepository().saveAll(); + } catch (Exception e) { + plugin.getLogger().warning("Failed to save all AFK times to storage repository: " + e.getMessage()); + } + return; + } + + flushPending(plugin, true); + } + + public static void shutdown() { + EzAfk plugin = Registry.get().getPlugin(); + stopFlushTask(); + if (plugin == null) { + return; + } + flushPending(plugin, true); + + if (Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().shutdown(); + } catch (Exception ignored) {} + } + } + + public static void recordAfkSession(UUID playerId, long startMillis, long endMillis) { + if (playerId == null || startMillis <= 0L) { + return; + } + + long durationMillis = endMillis - startMillis; + if (durationMillis <= 0L) { + return; + } + + long durationSeconds = durationMillis / 1000L; + if (durationSeconds <= 0L) { + return; + } + + addAfkDuration(playerId, durationSeconds); + } + + public static void flushActiveSessions(Map sessions) { + if (sessions == null || sessions.isEmpty()) { + return; + } + + long now = System.currentTimeMillis(); + for (Map.Entry entry : sessions.entrySet()) { + recordAfkSession(entry.getKey(), entry.getValue(), now); + } + } + + public static long getTotalAfkSeconds(UUID playerId) { + Long v = totalAfkSeconds.get(playerId); + if (v != null) return v; + + // Fallback: if not cached, query configured storage repository + if (Registry.get() != null && Registry.get().getStorageRepository() != null && playerId != null) { + try { + long stored = Registry.get().getStorageRepository().getPlayerAfkTime(playerId); + if (stored > 0L) { + totalAfkSeconds.put(playerId, stored); + updateLeaderboardEntry(playerId, stored); + } + return stored; + } catch (Exception e) { + if (Registry.get() != null) Registry.get().getLogger().warning("Failed to read AFK time from storage repository: " + e.getMessage()); + } + } + + return 0L; + } + + public static List> getTopPlayers(int limit) { + if (limit <= 0) { + return snapshotLeaderboard(leaderboardCache.size()); + } + + if (limit <= leaderboardCache.size()) { + return snapshotLeaderboard(limit); + } + + return buildSortedEntries(limit); + } + + private static void addAfkDuration(UUID playerId, long seconds) { + if (playerId == null || seconds <= 0L) { + return; + } + + long updatedTotal = totalAfkSeconds.merge(playerId, seconds, Long::sum); + updateLeaderboardEntry(playerId, updatedTotal); + queueSave(playerId, updatedTotal); + } + + private static boolean savePlayer(UUID playerId, long total, EzAfk plugin) { + if (playerId == null) return false; + + // If total is zero or less, remove/zero the record in storage + if (total <= 0L) { + removeFromLeaderboard(playerId); + if (Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().deletePlayer(playerId); + dirtyPlayers.remove(playerId); + return true; + } catch (Exception e) { + if (plugin != null) plugin.getLogger().warning("Failed to delete AFK time via storage repository: " + e.getMessage()); + return false; + } + } + if (plugin != null) plugin.getLogger().warning("No storage repository configured to delete AFK time for player " + playerId); + return false; + } + + if (persistPlayer(playerId, total, plugin)) { + dirtyPlayers.remove(playerId); + return true; + } + + dirtyPlayers.add(playerId); + return false; + } + + private static void queueSave(UUID playerId, long total) { + if (playerId == null) { + return; + } + + pendingSaves.put(playerId, total); + dirtyPlayers.add(playerId); + } + + private static void flushPending(EzAfk plugin, boolean synchronous) { + if (plugin == null) { + return; + } + + Map snapshot; + synchronized (pendingSaves) { + if (pendingSaves.isEmpty()) { + return; + } + snapshot = new HashMap<>(pendingSaves); + pendingSaves.clear(); + } + + if (synchronous) { + persistSnapshot(snapshot, plugin); + } else { + CompletableFuture.runAsync(() -> persistSnapshot(snapshot, plugin)); + } + } + + private static void persistSnapshot(Map snapshot, EzAfk plugin) { + for (Map.Entry entry : snapshot.entrySet()) { + if (!savePlayer(entry.getKey(), entry.getValue(), plugin)) { + queueSave(entry.getKey(), entry.getValue()); + } + } + } + + private static void configureFlushTask(EzAfk plugin) { + if (plugin == null) { + return; + } + + long intervalSeconds = plugin.getConfig().getLong("storage.flush-interval-seconds", DEFAULT_FLUSH_INTERVAL_SECONDS); + if (intervalSeconds <= 0L) { + intervalSeconds = DEFAULT_FLUSH_INTERVAL_SECONDS; + } + + flushIntervalTicks = Math.max(20L, intervalSeconds * 20L); + + flushTask = Registry.get().getScheduler().runTaskTimerAsync( + () -> flushPending(plugin, false), + flushIntervalTicks, flushIntervalTicks); + } + + private static void stopFlushTask() { + if (flushTask != null) { + flushTask.cancel(); + flushTask = null; + } + } + + private static boolean persistPlayer(UUID playerId, long total, EzAfk plugin) { + if (playerId == null) return false; + + if (Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().savePlayerAfkTime(playerId, total); + return true; + } catch (Exception e) { + if (plugin != null) plugin.getLogger().warning("Failed to persist AFK time via storage repository: " + e.getMessage()); + return false; + } + } + + if (plugin != null) plugin.getLogger().warning("No storage repository configured; cannot persist AFK time for player " + playerId); + return false; + } + + private static void rebuildLeaderboardCache() { + clearLeaderboardCache(); + for (Map.Entry entry : totalAfkSeconds.entrySet()) { + if (entry.getValue() != null && entry.getValue() > 0L) { + updateLeaderboardEntry(entry.getKey(), entry.getValue()); + } + } + } + + private static void clearLeaderboardCache() { + leaderboardCache.clear(); + leaderboardEntries.clear(); + } + + private static List> snapshotLeaderboard(int limit) { + int effectiveLimit = limit; + if (effectiveLimit <= 0 || effectiveLimit > leaderboardCache.size()) { + effectiveLimit = leaderboardCache.size(); + } + + List> results = new ArrayList<>(effectiveLimit); + int count = 0; + for (LeaderboardEntry entry : leaderboardCache) { + if (count >= effectiveLimit) { + break; + } + results.add(Map.entry(entry.playerId, entry.totalSeconds)); + count++; + } + + return results; + } + + private static List> buildSortedEntries(int limit) { + List> entries = new ArrayList<>(totalAfkSeconds.entrySet()); + entries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + + if (limit <= 0 || limit >= entries.size()) { + return entries; + } + + return new ArrayList<>(entries.subList(0, limit)); + } + + private static void updateLeaderboardEntry(UUID playerId, long total) { + LeaderboardEntry existing = leaderboardEntries.remove(playerId); + if (existing != null) { + leaderboardCache.remove(existing); + } + + if (total <= 0L) { + return; + } + + LeaderboardEntry entry = new LeaderboardEntry(playerId, total); + leaderboardCache.add(entry); + leaderboardEntries.put(playerId, entry); + + if (leaderboardCache.size() > LEADERBOARD_CACHE_SIZE) { + LeaderboardEntry removed = leaderboardCache.pollLast(); + if (removed != null) { + leaderboardEntries.remove(removed.playerId); + } + } + } + + private static void removeFromLeaderboard(UUID playerId) { + LeaderboardEntry existing = leaderboardEntries.remove(playerId); + if (existing != null) { + leaderboardCache.remove(existing); + } + } + + public static boolean resetPlayer(java.util.UUID playerId) { + if (playerId == null) return false; + + totalAfkSeconds.remove(playerId); + dirtyPlayers.remove(playerId); + pendingSaves.remove(playerId); + removeFromLeaderboard(playerId); + + if (Registry.get() != null && Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().deletePlayer(playerId); + return true; + } catch (Exception e) { + if (Registry.get() != null) Registry.get().getLogger().warning("Failed to delete player AFK time: " + e.getMessage()); + return false; + } + } + + return true; + } + + private static final class LeaderboardEntry { + private final UUID playerId; + private final long totalSeconds; + + LeaderboardEntry(UUID playerId, long totalSeconds) { + this.playerId = playerId; + this.totalSeconds = totalSeconds; + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneManager.java new file mode 100644 index 0000000..6e9f2cc --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneManager.java @@ -0,0 +1,221 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import com.gyvex.ezafk.zone.Zone; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class AfkZoneManager { + private static final List zones = new ArrayList<>(); + private AfkZoneManager() {} + + public static void load(EzAfk plugin) { + zones.clear(); + if (plugin == null) return; + + FileConfiguration zonesConfig = Registry.get().getZonesConfig(); + if (zonesConfig == null) return; + + if (!zonesConfig.getBoolean("enabled", false)) return; + + // Read global defaults from the top-level "defaults.reward" and + // "defaults.notification" sections so zone authors can opt-in to + // sensible behaviour without repeating config per zone. + Map defaultsRewardMap = null; + Map defaultsNotifMap = null; + Object defaultsObj = zonesConfig.get("defaults"); + if (defaultsObj instanceof Map) { + @SuppressWarnings("unchecked") + Map defaultsMap = (Map) defaultsObj; + Object dr = defaultsMap.get("reward"); + if (dr instanceof Map) { + @SuppressWarnings("unchecked") + Map drm = (Map) dr; + defaultsRewardMap = drm; + Object dn = drm.get("notification"); + if (dn instanceof Map) { + @SuppressWarnings("unchecked") + Map dnm = (Map) dn; + defaultsNotifMap = dnm; + } + } + } + + // Default notification values (can be overridden by defaults.reward.notification or per-zone) + boolean defaultNotifEnabled = true; + List defaultNotifDisplays = new ArrayList<>(); + defaultNotifDisplays.add("ACTION_BAR"); + String defaultNotifMessage = "&7Next reward in &e{seconds}s &7in &a%zone%"; + int defaultNotifDuration = 0; // 0 = use zone interval + + if (defaultsNotifMap != null) { + Object en = defaultsNotifMap.get("enabled"); + if (en != null) defaultNotifEnabled = Boolean.parseBoolean(String.valueOf(en)); + Object disp = defaultsNotifMap.get("displays"); + if (disp instanceof List) { + defaultNotifDisplays = new ArrayList<>(); + for (Object d : (List) disp) defaultNotifDisplays.add(String.valueOf(d).toUpperCase()); + } + Object msg = defaultsNotifMap.get("message"); + if (msg != null) defaultNotifMessage = String.valueOf(msg); + Object dur = defaultsNotifMap.get("duration"); + if (dur != null) defaultNotifDuration = (int) toDouble(dur); + } + + // Default reward values from "defaults.reward" + boolean defaultRewardEnabled = false; + long defaultRewardInterval = 300L; + double defaultRewardAmount = 1.0; + String defaultRewardType = "economy"; + + if (defaultsRewardMap != null) { + Object en = defaultsRewardMap.get("enabled"); + if (en != null) defaultRewardEnabled = Boolean.parseBoolean(String.valueOf(en)); + Object iv = defaultsRewardMap.get("interval-seconds"); + if (iv != null) defaultRewardInterval = (long) toDouble(iv); + Object am = defaultsRewardMap.get("amount"); + if (am != null) defaultRewardAmount = toDouble(am); + Object ty = defaultsRewardMap.get("type"); + if (ty != null) defaultRewardType = String.valueOf(ty); + } + + List list = zonesConfig.getList("regions"); + if (list == null) return; + + for (Object o : list) { + if (!(o instanceof Map)) continue; + @SuppressWarnings("unchecked") + Map map = (Map) o; + String name = (String) map.getOrDefault("name", ""); + String worldName = (String) map.getOrDefault("world", "world"); + double x1 = toDouble(map.getOrDefault("x1", 0)); + double y1 = toDouble(map.getOrDefault("y1", 0)); + double z1 = toDouble(map.getOrDefault("z1", 0)); + double x2 = toDouble(map.getOrDefault("x2", 0)); + double y2 = toDouble(map.getOrDefault("y2", 0)); + double z2 = toDouble(map.getOrDefault("z2", 0)); + + boolean rewardEnabled = defaultRewardEnabled; + long rewardInterval = defaultRewardInterval; + int rewardMaxStack = 0; + double rewardAmount = defaultRewardAmount; + String rewardType = defaultRewardType; + String rewardCommand = null; + String rewardItemMaterial = null; + int rewardItemAmount = 1; + int rewardLimit = 0; + long rewardLimitCooldown = 0L; + + boolean notifEnabled = defaultNotifEnabled; + List notifDisplays = new ArrayList<>(defaultNotifDisplays); + String notifMessage = defaultNotifMessage; + int notifDuration = defaultNotifDuration; + + if (map.containsKey("reward") && map.get("reward") instanceof Map) { + @SuppressWarnings("unchecked") + Map rewardMap = (Map) map.get("reward"); + rewardEnabled = Boolean.parseBoolean(String.valueOf(rewardMap.getOrDefault("enabled", rewardEnabled))); + rewardInterval = (long) toDouble(rewardMap.getOrDefault("interval-seconds", rewardInterval)); + Object maxStackObj = rewardMap.getOrDefault("max-stack", 0); + if (maxStackObj instanceof Number) { + rewardMaxStack = ((Number) maxStackObj).intValue(); + } else { + try { rewardMaxStack = Integer.parseInt(String.valueOf(maxStackObj)); } catch (Exception ignored) {} + } + rewardAmount = toDouble(rewardMap.getOrDefault("amount", rewardAmount)); + rewardType = String.valueOf(rewardMap.getOrDefault("type", rewardType)); + rewardCommand = rewardMap.getOrDefault("command", null) != null ? String.valueOf(rewardMap.get("command")) : null; + + if (rewardMap.containsKey("item") && rewardMap.get("item") instanceof Map) { + @SuppressWarnings("unchecked") + Map itemMap = (Map) rewardMap.get("item"); + rewardItemMaterial = String.valueOf(itemMap.getOrDefault("material", "")); + Object amt = itemMap.getOrDefault("amount", 1); + if (amt instanceof Number) rewardItemAmount = ((Number) amt).intValue(); + else try { rewardItemAmount = Integer.parseInt(String.valueOf(amt)); } catch (Exception ignored) {} + } + Object limitObj = rewardMap.getOrDefault("limit", 0); + if (limitObj instanceof Number) rewardLimit = ((Number) limitObj).intValue(); + else try { rewardLimit = Integer.parseInt(String.valueOf(limitObj)); } catch (Exception ignored) {} + + rewardLimitCooldown = (long) toDouble(rewardMap.getOrDefault("limit-cooldown-seconds", 0)); + + // Per-zone notification overrides + if (rewardMap.containsKey("notification") && rewardMap.get("notification") instanceof Map) { + @SuppressWarnings("unchecked") + Map notifMap = (Map) rewardMap.get("notification"); + Object en = notifMap.get("enabled"); + if (en != null) notifEnabled = Boolean.parseBoolean(String.valueOf(en)); + Object disp = notifMap.get("displays"); + if (disp instanceof List) { + notifDisplays = new ArrayList<>(); + for (Object d : (List) disp) notifDisplays.add(String.valueOf(d).toUpperCase()); + } + Object msg = notifMap.get("message"); + if (msg != null) notifMessage = String.valueOf(msg); + Object dur = notifMap.get("duration"); + if (dur != null) notifDuration = (int) toDouble(dur); + } + } + + World world = Bukkit.getWorld(worldName); + if (world == null) continue; + + zones.add(new Zone( + name, world.getName(), + Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2), + Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2), + rewardEnabled, rewardInterval, rewardMaxStack, rewardAmount, + rewardType, rewardCommand, rewardItemMaterial, rewardItemAmount, + rewardLimit, rewardLimitCooldown, + notifEnabled, notifDisplays, notifMessage, notifDuration + )); + } + } + + private static double toDouble(Object o) { + if (o instanceof Number) return ((Number) o).doubleValue(); + try { + return Double.parseDouble(String.valueOf(o)); + } catch (Exception e) { + return 0d; + } + } + + public static boolean isInAfkZone(Player player) { + if (player == null) return false; + if (zones.isEmpty()) return false; + + for (Zone z : zones) { + if (z.contains(player)) return true; + } + + return false; + } + + public static Zone getZoneForPlayer(Player player) { + if (player == null || zones.isEmpty()) return null; + + for (Zone z : zones) { + if (z.contains(player)) return z; + } + + return null; + } + + /** + * Returns a copy of the currently loaded zones. + */ + public static java.util.List getZones() { + return new java.util.ArrayList<>(zones); + } + + // Zone class moved to com.gyvex.ezafk.manager.zone.Zone +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneRewardManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneRewardManager.java new file mode 100644 index 0000000..4ceb76a --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/AfkZoneRewardManager.java @@ -0,0 +1,363 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.integration.Integration; +import com.gyvex.ezafk.integration.EconomyIntegration; +import com.gyvex.ezafk.integration.ezcountdown.EzCountdownIntegration; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import com.gyvex.ezafk.zone.Zone; +import net.milkbowl.vault.economy.Economy; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +public final class AfkZoneRewardManager { + private static final Map> states = new HashMap<>(); + + private AfkZoneRewardManager() {} + + public static void processRewards() { + EzAfk plugin = Registry.get().getPlugin(); + if (plugin == null) return; + + long now = System.currentTimeMillis(); + + for (UUID playerId : new HashSet<>(AfkState.afkPlayers)) { + Player player = Bukkit.getPlayer(playerId); + if (player == null) continue; + + Zone zone = AfkZoneManager.getZoneForPlayer(player); + if (zone == null || !zone.rewardEnabled || zone.rewardIntervalSeconds <= 0 || zone.rewardAmount <= 0.0) { + // clear any saved state for this player/zone + Map m = states.get(playerId); + if (m != null && zone != null) { + m.remove(zone.name); + stopZoneCountdown(playerId, zone); + } + continue; + } + + Map playerStates = states.computeIfAbsent(playerId, k -> new HashMap<>()); + RewardState rs = playerStates.get(zone.name); + + if (rs == null) { + rs = new RewardState(now + zone.rewardIntervalSeconds * 1000L, 0, 0, 0L); + playerStates.put(zone.name, rs); + // Start initial countdown to first reward + startZoneCountdown(player, zone); + continue; // first interval scheduled + } + + if (now < rs.nextScheduled) { + continue; // not yet + } + + // If a per-player limit is configured, check cooldown and reset if expired + if (zone.rewardLimit > 0) { + if (rs.limitCooldownUntil > now) { + // still in cooldown, notify player and schedule next check + long remainingMs = rs.limitCooldownUntil - now; + String timeStr = formatDurationMillis(remainingMs); + try { + Map ph = new HashMap<>(); + ph.put("reward_limit", String.valueOf(zone.rewardLimit)); + ph.put("reward_name", zone.name == null ? "" : zone.name); + ph.put("time_remaining", timeStr); + MessageManager.sendMessage(player, "afkzone.reward.limit.cooldown", "&cYou already reached the %reward_limit% for %reward_name%, come back in %time_remaining%.", ph); + } catch (Throwable ignored) {} + rs.nextScheduled = now + Math.max(1000L, zone.rewardIntervalSeconds * 1000L); + continue; + } else { + // cooldown expired - reset count + if (rs.grantCount >= zone.rewardLimit) { + rs.grantCount = 0; + rs.limitCooldownUntil = 0L; + } + } + } + + long intervalMs = Math.max(1L, zone.rewardIntervalSeconds) * 1000L; + long elapsed = now - rs.nextScheduled; + long intervalsPassed = 1 + (elapsed / intervalMs); + + int toGrant = (int) Math.min(intervalsPassed, Integer.MAX_VALUE); + if (zone.rewardMaxStack > 0) { + // cap by max stack + toGrant = Math.min(toGrant, Math.max(0, zone.rewardMaxStack - rs.stackCount)); + } + // cap by per-player limit if configured + if (zone.rewardLimit > 0) { + int remaining = Math.max(0, zone.rewardLimit - rs.grantCount); + toGrant = Math.min(toGrant, remaining); + } + + if (toGrant <= 0) { + // nothing to grant (stack full or limit reached) + if (zone.rewardLimit > 0 && rs.grantCount >= zone.rewardLimit) { + // set cooldown if configured + if (zone.rewardLimitCooldownSeconds > 0) { + rs.limitCooldownUntil = now + zone.rewardLimitCooldownSeconds * 1000L; + } + // notify player + try { + Map ph = new HashMap<>(); + ph.put("reward_limit", String.valueOf(zone.rewardLimit)); + ph.put("reward_name", zone.name == null ? "" : zone.name); + ph.put("time_remaining", formatDurationMillis(Math.max(0, rs.limitCooldownUntil - now))); + MessageManager.sendMessage(player, "afkzone.reward.limit.reached", "&cYou already reached the %reward_limit% for %reward_name%, come back in %time_remaining%.", ph); + } catch (Throwable ignored) {} + } + rs.nextScheduled = now + intervalMs; // schedule next check + continue; + } + + // Try to grant rewards (economy deposit) + Economy econ = null; + if (IntegrationManager.hasIntegration("economy")) { + try { + Integration integration = (Integration) IntegrationManager.getIntegration("economy"); + if (integration instanceof EconomyIntegration) { + econ = ((EconomyIntegration) integration).getEconomy(); + } + } catch (Throwable ignored) {} + } + + boolean grantSuccess = false; + String grantFailReason = null; + String rewardType = zone.rewardType == null ? "economy" : zone.rewardType.toLowerCase(); + double grantedAmount = 0.0; + + if ("economy".equals(rewardType)) { + double totalAmount = zone.rewardAmount * toGrant; + if (econ != null) { + try { + net.milkbowl.vault.economy.EconomyResponse resp = econ.depositPlayer(player, totalAmount); + grantSuccess = resp.transactionSuccess(); + if (grantSuccess) { + grantedAmount = totalAmount; + } else { + grantFailReason = resp.errorMessage == null ? "transaction-failed" : "transaction-failed: " + resp.errorMessage; + } + } catch (Throwable ex) { + grantSuccess = false; + grantFailReason = "exception: " + ex.getClass().getSimpleName(); + } + } else { + plugin.getLogger().log(Level.FINE, "No economy provider available for AFK zone rewards (zone=" + zone.name + ")"); + grantSuccess = false; + grantFailReason = "no-economy-provider"; + } + if (grantSuccess) { + plugin.getLogger().log(Level.FINE, "Granted AFK zone economy reward to " + player.getName() + " zone=" + zone.name + " amount=" + grantedAmount + " (" + toGrant + ")"); + } + } else if ("command".equals(rewardType)) { + // Execute configured command as console, replacing %player% and %amount% + if (zone.rewardCommand != null && !zone.rewardCommand.isBlank()) { + for (int i = 0; i < toGrant; i++) { + String cmd = zone.rewardCommand.replace("%player%", player.getName()); + cmd = cmd.replace("%amount%", String.valueOf(zone.rewardAmount)); + try { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd); + } catch (Throwable ex) { + plugin.getLogger().log(Level.FINE, "Failed to execute AFK zone command reward: " + cmd); + } + } + grantSuccess = true; + plugin.getLogger().log(Level.FINE, "Executed AFK zone command reward for " + player.getName() + " zone=" + zone.name + " (" + toGrant + ")"); + } + } else if ("item".equals(rewardType)) { + if (zone.rewardItemMaterial != null && !zone.rewardItemMaterial.isBlank()) { + try { + Material mat = Material.matchMaterial(zone.rewardItemMaterial); + if (mat != null) { + int totalItems = zone.rewardItemAmount * toGrant; + ItemStack stack = new ItemStack(mat, Math.max(1, Math.min(64, totalItems))); + Map leftover = player.getInventory().addItem(stack); + // If leftover, drop on ground + if (!leftover.isEmpty()) { + for (ItemStack s : leftover.values()) { + player.getWorld().dropItemNaturally(player.getLocation(), s); + } + } + grantSuccess = true; + plugin.getLogger().log(Level.FINE, "Granted AFK zone item reward to " + player.getName() + " zone=" + zone.name + " item=" + zone.rewardItemMaterial + " count=" + totalItems); + } else { + plugin.getLogger().log(Level.FINE, "Invalid material for AFK zone item reward: " + zone.rewardItemMaterial); + } + } catch (Throwable ex) { + plugin.getLogger().log(Level.FINE, "Failed to grant AFK zone item reward: " + ex.getMessage()); + } + } + } else { + plugin.getLogger().log(Level.FINE, "Unknown reward type for AFK zone: " + rewardType); + } + + if (grantSuccess) { + rs.stackCount += toGrant; + rs.grantCount += toGrant; + rs.nextScheduled = now + intervalMs; + + // Track session stats + ZoneRewardStatsManager.recordGrant(playerId, zone.name, toGrant, grantedAmount); + + // If grant count reached limit, start cooldown and notify + if (zone.rewardLimit > 0 && rs.grantCount >= zone.rewardLimit) { + if (zone.rewardLimitCooldownSeconds > 0) { + rs.limitCooldownUntil = now + zone.rewardLimitCooldownSeconds * 1000L; + } + try { + Map ph = new HashMap<>(); + ph.put("reward_limit", String.valueOf(zone.rewardLimit)); + ph.put("reward_name", zone.name == null ? "" : zone.name); + ph.put("time_remaining", formatDurationMillis(Math.max(0, rs.limitCooldownUntil - now))); + MessageManager.sendMessage(player, "afkzone.reward.limit.reached", "&cYou already reached the %reward_limit% for %reward_name%, come back in %time_remaining%.", ph); + } catch (Throwable ignored) {} + } + + // Send localized player message about the reward + try { + Map placeholders = new HashMap<>(); + placeholders.put("zone", zone.name == null || zone.name.isEmpty() ? "" : zone.name); + if ("economy".equals(rewardType)) { + DecimalFormat df = new DecimalFormat("0.##"); + String formatted = df.format(grantedAmount); + placeholders.put("amount", formatted); + MessageManager.sendMessage(player, "afkzone.reward.granted.economy", "&aYou received %amount% for staying AFK in %zone%.", placeholders); + } else if ("command".equals(rewardType)) { + placeholders.put("amount", String.valueOf(zone.rewardAmount)); + MessageManager.sendMessage(player, "afkzone.reward.granted.command", "&aYou received a command reward for staying AFK in %zone%.", placeholders); + } else if ("item".equals(rewardType)) { + placeholders.put("item", zone.rewardItemMaterial == null ? "" : zone.rewardItemMaterial); + placeholders.put("count", String.valueOf(zone.rewardItemAmount * toGrant)); + MessageManager.sendMessage(player, "afkzone.reward.granted.item", "&aYou received %count%x %item% for staying AFK in %zone%.", placeholders); + } else { + MessageManager.sendMessage(player, "afkzone.reward.granted", "&aYou received a reward for staying AFK in %zone%.", placeholders); + } + } catch (Throwable ignored) { + } + + // Restart EzCountdown zone reward countdown so the player sees + // the timer to their NEXT reward in real time. + startZoneCountdown(player, zone); + + } else { + rs.nextScheduled = now + intervalMs; // schedule next attempt + // If the player is an operator, notify them about the failure to aid debugging + try { + if (player.isOp()) { + Map ph = new HashMap<>(); + ph.put("zone", zone.name == null ? "" : zone.name); + ph.put("reason", grantFailReason == null ? "unknown" : grantFailReason); + MessageManager.sendMessage(player, "afkzone.reward.failed", "&cAFK zone reward failed for %zone%: %reason%", ph); + } + } catch (Throwable ignored) {} + } + } + } + + /** + * Starts (or restarts) the EzCountdown zone-reward countdown for the given + * player/zone when EzCountdown is available and notification is configured. + * Silently ignored when EzCountdown is not installed or notification is + * disabled for the zone. + */ + private static void startZoneCountdown(Player player, Zone zone) { + if (!zone.notificationEnabled || zone.notificationDisplays.isEmpty()) return; + if (!IntegrationManager.hasIntegration("ezcountdown")) return; + try { + Integration raw = IntegrationManager.getIntegration("ezcountdown"); + if (!(raw instanceof EzCountdownIntegration)) return; + EzCountdownIntegration ezcd = (EzCountdownIntegration) raw; + int duration = zone.notificationDuration > 0 + ? zone.notificationDuration + : (int) Math.min(zone.rewardIntervalSeconds, Integer.MAX_VALUE); + DecimalFormat df = new DecimalFormat("0.##"); + String amountStr = df.format(zone.rewardAmount); + ezcd.sendZoneRewardCountdown(player, zone.name, zone.notificationMessage, + duration, zone.notificationDisplays, zone.name, amountStr); + } catch (Throwable ignored) {} + } + + /** + * Stops the active EzCountdown zone-reward countdown for the given player/zone. + * Silently ignored when EzCountdown is not installed. + */ + private static void stopZoneCountdown(UUID playerId, Zone zone) { + if (!IntegrationManager.hasIntegration("ezcountdown")) return; + try { + Integration raw = IntegrationManager.getIntegration("ezcountdown"); + if (!(raw instanceof EzCountdownIntegration)) return; + ((EzCountdownIntegration) raw).removeZoneRewardCountdown(playerId, zone.name); + } catch (Throwable ignored) {} + } + + /** + * Stops all active EzCountdown zone-reward countdowns for the given player + * (e.g. on player quit or global unAFK). + */ + public static void stopAllZoneCountdowns(UUID playerId) { + if (!IntegrationManager.hasIntegration("ezcountdown")) return; + try { + Integration raw = IntegrationManager.getIntegration("ezcountdown"); + if (!(raw instanceof EzCountdownIntegration)) return; + ((EzCountdownIntegration) raw).removeAllZoneCountdowns(playerId); + } catch (Throwable ignored) {} + states.remove(playerId); + } + + private static final class RewardState { + long nextScheduled; + int stackCount; + int grantCount; + long limitCooldownUntil; + + RewardState(long nextScheduled, int stackCount, int grantCount, long limitCooldownUntil) { + this.nextScheduled = nextScheduled; + this.stackCount = stackCount; + this.grantCount = grantCount; + this.limitCooldownUntil = limitCooldownUntil; + } + } + + public static void resetPlayerCounts(java.util.UUID playerId) { + Map m = states.get(playerId); + if (m != null) { + for (RewardState rs : m.values()) { + rs.grantCount = 0; + rs.limitCooldownUntil = 0L; + } + } + } + + public static void resetPlayerCountForZone(java.util.UUID playerId, String zoneName) { + Map m = states.get(playerId); + if (m != null) { + RewardState rs = m.get(zoneName); + if (rs != null) { + rs.grantCount = 0; + rs.limitCooldownUntil = 0L; + } + } + } + + private static String formatDurationMillis(long ms) { + if (ms <= 0) return "0s"; + long s = ms / 1000; + long hours = s / 3600; + long minutes = (s % 3600) / 60; + long seconds = s % 60; + if (hours > 0) return String.format("%dh %dm %ds", hours, minutes, seconds); + if (minutes > 0) return String.format("%dm %ds", minutes, seconds); + return String.format("%ds", seconds); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassList.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassList.java new file mode 100644 index 0000000..90e1409 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassList.java @@ -0,0 +1,77 @@ +package com.gyvex.ezafk.manager; + +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +/** + * A single named bypass list that reads from and writes to one YAML section. + * + *

Instances are owned by {@link BypassListManager}; this class is + * package-private so callers use the manager's static API.

+ */ +final class BypassList { + + private final String yamlKey; + private final Set entries = new HashSet<>(); + + BypassList(String yamlKey) { + this.yamlKey = yamlKey; + } + + // ── Mutations ───────────────────────────────────────────────────────────── + + /** @return {@code true} if the id was not already present. */ + boolean add(UUID id) { + return entries.add(id); + } + + /** @return {@code true} if the id was present and has been removed. */ + boolean remove(UUID id) { + return entries.remove(id); + } + + boolean contains(UUID id) { + return entries.contains(id); + } + + /** Returns an immutable snapshot of the current entries. */ + Set snapshot() { + return Collections.unmodifiableSet(entries); + } + + void clear() { + entries.clear(); + } + + // ── YAML I/O ───────────────────────────────────────────────────────────── + + /** + * Populates this list from the named section in {@code cfg}. + * Malformed UUID strings are silently skipped. + */ + void read(FileConfiguration cfg) { + entries.clear(); + for (String s : cfg.getStringList(yamlKey)) { + try { + entries.add(UUID.fromString(s)); + } catch (IllegalArgumentException ignored) { + // skip malformed entries + } + } + } + + /** Serialises this list's entries to the named section in {@code cfg}. */ + void write(FileConfiguration cfg) { + List out = new ArrayList<>(entries.size()); + for (UUID id : entries) { + out.add(id.toString()); + } + cfg.set(yamlKey, out); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassListManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassListManager.java new file mode 100644 index 0000000..e602b34 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/BypassListManager.java @@ -0,0 +1,119 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.UUID; + +/** + * Persistent bypass whitelist and blacklist backed by {@code bypass-lists.yml}. + * + *
    + *
  • Whitelist – players that always bypass AFK detection regardless of the + * {@code afk.bypass.enabled} config value or the {@code ezafk.bypass} permission.
  • + *
  • Blacklist – players that are never allowed to bypass AFK detection, even when + * they hold the {@code ezafk.bypass} permission (useful for ops who want to be subject + * to AFK detection / zone rewards).
  • + *
+ * + *

Blacklist takes precedence over whitelist. Each list is backed by a {@link BypassList} + * instance which handles data storage and YAML serialisation for that list independently.

+ */ +public final class BypassListManager { + + private static final BypassList WHITELIST = new BypassList("whitelist"); + private static final BypassList BLACKLIST = new BypassList("blacklist"); + /** Package-private so tests in the same package can point it at a temp file. */ + static File dataFile; + + private BypassListManager() {} + + // ── Lifecycle ───────────────────────────────────────────────────────────── + + public static void load(EzAfk plugin) { + dataFile = new File(plugin.getDataFolder(), "bypass-lists.yml"); + if (!dataFile.exists()) { + try { + plugin.getDataFolder().mkdirs(); + dataFile.createNewFile(); + } catch (IOException ignored) {} + } + loadFromFile(dataFile); + } + + /** Package-private: load from an explicit file without needing a plugin instance (used in tests). */ + static void loadFromFile(File file) { + FileConfiguration cfg = YamlConfiguration.loadConfiguration(file); + WHITELIST.read(cfg); + BLACKLIST.read(cfg); + } + + /** Package-private: clear all in-memory state and reset the data-file pointer (used in tests). */ + public static void reset() { + WHITELIST.clear(); + BLACKLIST.clear(); + dataFile = null; + } + + private static void save() { + if (dataFile == null) return; + FileConfiguration cfg = new YamlConfiguration(); + WHITELIST.write(cfg); + BLACKLIST.write(cfg); + try { + cfg.save(dataFile); + } catch (IOException ignored) {} + } + + // ── Whitelist ───────────────────────────────────────────────────────────── + + /** @return {@code true} if the player was added (was not already present). */ + public static boolean addToWhitelist(UUID id) { + boolean added = WHITELIST.add(id); + if (added) save(); + return added; + } + + /** @return {@code true} if the player was removed (was present). */ + public static boolean removeFromWhitelist(UUID id) { + boolean removed = WHITELIST.remove(id); + if (removed) save(); + return removed; + } + + public static boolean isWhitelisted(UUID id) { + return WHITELIST.contains(id); + } + + public static Set getWhitelist() { + return WHITELIST.snapshot(); + } + + // ── Blacklist ───────────────────────────────────────────────────────────── + + /** @return {@code true} if the player was added (was not already present). */ + public static boolean addToBlacklist(UUID id) { + boolean added = BLACKLIST.add(id); + if (added) save(); + return added; + } + + /** @return {@code true} if the player was removed (was present). */ + public static boolean removeFromBlacklist(UUID id) { + boolean removed = BLACKLIST.remove(id); + if (removed) save(); + return removed; + } + + public static boolean isBlacklisted(UUID id) { + return BLACKLIST.contains(id); + } + + public static Set getBlacklist() { + return BLACKLIST.snapshot(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/EconomyManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/EconomyManager.java new file mode 100644 index 0000000..d2e5edb --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/EconomyManager.java @@ -0,0 +1,397 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.integration.EconomyIntegration; +import com.gyvex.ezafk.integration.Integration; +import com.gyvex.ezafk.state.AfkState; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.economy.EconomyResponse; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; + +public final class EconomyManager { + private static final Map nextRecurringCharge = new HashMap<>(); + private static final Map blockedUntil = new HashMap<>(); + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.##"); + + private EconomyManager() { + } + + public static void reset() { + nextRecurringCharge.clear(); + blockedUntil.clear(); + } + + public static void onActivity(Player player) { + if (player == null) { + return; + } + + blockedUntil.remove(player.getUniqueId()); + } + + public static void onDisable(UUID playerId) { + if (playerId == null) { + return; + } + + nextRecurringCharge.remove(playerId); + blockedUntil.remove(playerId); + } + + public static boolean isEconomyBlocked(Player player) { + if (player == null) { + return false; + } + + if (!isEconomyActive()) { + return false; + } + + UUID playerId = player.getUniqueId(); + Long until = blockedUntil.get(playerId); + + if (until == null) { + return false; + } + + if (System.currentTimeMillis() >= until) { + blockedUntil.remove(playerId); + return false; + } + + return true; + } + + public static boolean handleEnter(Player player, boolean initiatedByPlayer) { + if (player == null) { + return false; + } + + EzAfk plugin = Registry.get().getPlugin(); + + if (!isEconomyActive() || bypassesEconomy(player)) { + scheduleNextCharge(player, plugin); + return true; + } + + ConfigurationSection section = plugin.getConfig().getConfigurationSection("economy.cost.enter"); + + if (section == null || !section.getBoolean("enabled", false)) { + scheduleNextCharge(player, plugin); + return true; + } + + double amount = section.getDouble("amount", 0D); + + if (amount <= 0) { + scheduleNextCharge(player, plugin); + return true; + } + + Economy economy = getEconomy(); + + if (economy == null) { + return true; + } + + boolean requireFunds = section.getBoolean("require-funds", true); + + boolean hasFunds = economy.has(player, amount); + + if (!hasFunds) { + if (requireFunds) { + if (initiatedByPlayer) { + sendEconomyMessage(player, "economy.enter.insufficient", "&cYou need %amount% to go AFK.", amount, economy); + } + handleFailure(player, initiatedByPlayer); + return false; + } + + logSkippedCharge(plugin, player, "insufficient funds"); + + sendEconomyMessage(player, "economy.enter.skipped", "&eThe AFK entry fee was skipped because your account couldn't be charged.", amount, economy); + + scheduleNextCharge(player, plugin); + return true; + } + + EconomyResponse response = economy.withdrawPlayer(player, amount); + + if (!response.transactionSuccess()) { + if (requireFunds) { + if (initiatedByPlayer) { + sendEconomyMessage(player, "economy.enter.failed", "&cWe couldn't charge your account to go AFK.", amount, economy); + } + handleFailure(player, initiatedByPlayer); + return false; + } + + String reason = response.errorMessage == null || response.errorMessage.isBlank() + ? "unknown Vault error" + : response.errorMessage; + logSkippedCharge(plugin, player, "Vault withdrawal failed: " + reason); + + sendEconomyMessage(player, "economy.enter.skipped", "&eThe AFK entry fee was skipped because your account couldn't be charged.", amount, economy); + + scheduleNextCharge(player, plugin); + return true; + } + + sendEconomyMessage(player, "economy.enter.charged", "&eYou paid %amount% to go AFK.", amount, economy); + scheduleNextCharge(player, plugin); + return true; + } + + public static void processRecurringCharges() { + if (!isEconomyActive() || !isRecurringEnabled()) { + return; + } + + EzAfk plugin = Registry.get().getPlugin(); + + if (plugin == null) { + return; + } + + ConfigurationSection section = plugin.getConfig().getConfigurationSection("economy.cost.recurring"); + + if (section == null) { + return; + } + + double amount = section.getDouble("amount", 0D); + + if (amount <= 0) { + return; + } + + long interval = Math.max(1L, section.getLong("interval", 60L)) * 1000L; + boolean requireFunds = section.getBoolean("require-funds", true); + boolean kickOnFail = section.getBoolean("kick-on-fail", false); + + Economy economy = getEconomy(); + + if (economy == null) { + return; + } + + long now = System.currentTimeMillis(); + + for (UUID playerId : new HashSet<>(AfkState.afkPlayers)) { + Player player = Bukkit.getPlayer(playerId); + + if (player == null) { + continue; + } + + if (bypassesEconomy(player)) { + continue; + } + + long scheduled = nextRecurringCharge.computeIfAbsent(playerId, id -> now + interval); + + if (now < scheduled) { + continue; + } + + nextRecurringCharge.put(playerId, now + interval); + + boolean hasFunds = economy.has(player, amount); + + if (!hasFunds) { + if (requireFunds) { + sendEconomyMessage(player, "economy.recurring.revoked-insufficient", "&cYou don't have enough funds to remain AFK, so your AFK status has been removed.", amount, economy); + AfkState.disable(plugin, player); + } else { + sendEconomyMessage(player, "economy.recurring.skipped", "&eThe recurring AFK fee was skipped because your account couldn't be charged. We'll try again later.", amount, economy); + if (kickOnFail) { + AfkState.disable(plugin, player); + } else { + blockPlayer(player, plugin); + } + } + continue; + } + + EconomyResponse response = economy.withdrawPlayer(player, amount); + + if (!response.transactionSuccess()) { + if (requireFunds) { + sendEconomyMessage(player, "economy.recurring.revoked-failed", "&cWe couldn't charge your account to remain AFK, so your AFK status has been removed.", amount, economy); + AfkState.disable(plugin, player); + } else { + sendEconomyMessage(player, "economy.recurring.failed", "&cWe couldn't charge your account to remain AFK. We'll try again later.", amount, economy); + if (kickOnFail) { + AfkState.disable(plugin, player); + } else { + blockPlayer(player, plugin); + } + } + continue; + } + + sendEconomyMessage(player, "economy.recurring.charged", "&eYou paid %amount% to remain AFK.", amount, economy); + } + } + + private static void blockPlayer(Player player, EzAfk plugin) { + long retryDelay = Math.max(0L, plugin.getConfig().getLong("economy.cost.enter.retry-delay", 60L)); + + if (retryDelay <= 0L) { + blockedUntil.remove(player.getUniqueId()); + return; + } + + blockedUntil.put(player.getUniqueId(), System.currentTimeMillis() + retryDelay * 1000L); + } + + private static void handleFailure(Player player, boolean initiatedByPlayer) { + EzAfk plugin = Registry.get().getPlugin(); + + if (!initiatedByPlayer && plugin != null) { + blockPlayer(player, plugin); + } + } + + private static void logSkippedCharge(EzAfk plugin, Player player, String reason) { + if (plugin == null || player == null) { + return; + } + + plugin.getLogger().fine(() -> "Skipping AFK entry charge for " + player.getName() + ": " + reason); + } + + private static void scheduleNextCharge(Player player, EzAfk plugin) { + if (player == null || plugin == null) { + return; + } + + if (!isRecurringEnabled()) { + nextRecurringCharge.remove(player.getUniqueId()); + return; + } + + long interval = Math.max(1L, plugin.getConfig().getLong("economy.cost.recurring.interval", 60L)) * 1000L; + nextRecurringCharge.put(player.getUniqueId(), System.currentTimeMillis() + interval); + } + + private static boolean bypassesEconomy(Player player) { + EzAfk plugin = Registry.get().getPlugin(); + + if (plugin == null) { + return false; + } + + // Use AfkBypassFlag to check WorldGuard regions directly instead of delegating + // to WorldGuardIntegration. Avoid compile-time references to WorldGuard + // classes by using reflection so the plugin can load without WorldGuard. + try { + Object flag = com.gyvex.ezafk.integration.worldguard.flag.AfkBypassFlag.get(); + if (flag != null) { + // WorldGuard.getInstance().getPlatform().getRegionContainer(); + Class worldGuardClass = Class.forName("com.sk89q.worldguard.WorldGuard"); + Object wgInstance = worldGuardClass.getMethod("getInstance").invoke(null); + Object platform = wgInstance.getClass().getMethod("getPlatform").invoke(wgInstance); + Object container = platform.getClass().getMethod("getRegionContainer").invoke(platform); + + // adapter for world -> region manager + Class bukkitAdapterClass = Class.forName("com.sk89q.worldedit.bukkit.BukkitAdapter"); + Object adaptedWorld = bukkitAdapterClass.getMethod("adapt", org.bukkit.World.class).invoke(null, player.getWorld()); + + Object regions = container.getClass().getMethod("get", adaptedWorld.getClass()).invoke(container, adaptedWorld); + if (regions != null) { + Object blockVector = bukkitAdapterClass.getMethod("asBlockVector", org.bukkit.Location.class).invoke(null, player.getLocation()); + Object set = regions.getClass().getMethod("getApplicableRegions", blockVector.getClass()).invoke(regions, blockVector); + // Iterate over set (it is Iterable) + for (Object region : (Iterable) set) { + try { + Object flagsMap = region.getClass().getMethod("getFlags").invoke(region); + boolean contains = (boolean) flagsMap.getClass().getMethod("containsKey", Object.class).invoke(flagsMap, flag); + if (contains) { + Object flagVal = region.getClass().getMethod("getFlag", flag.getClass()).invoke(region, flag); + // Compare to StateFlag.State.ALLOW + Class stateFlagClass = Class.forName("com.sk89q.worldguard.protection.flags.StateFlag"); + Class stateEnum = Class.forName("com.sk89q.worldguard.protection.flags.StateFlag$State"); + Object allow = java.lang.Enum.valueOf((Class) stateEnum, "ALLOW"); + if (allow.equals(flagVal)) { + return true; + } + } + } catch (NoSuchMethodException nsme) { + // older/newer API mismatch; ignore and continue + } + } + } + } + } catch (Throwable ignored) { + // WorldGuard not available or error while checking regions; fall through. + } + + String permission = plugin.getConfig().getString("economy.bypass-permission", ""); + + if (permission == null || permission.isBlank()) { + return false; + } + + return player.hasPermission(permission); + } + + private static boolean isEconomyActive() { + EzAfk plugin = Registry.get().getPlugin(); + + if (plugin == null) { + return false; + } + + if (!plugin.getConfig().getBoolean("economy.enabled")) { + return false; + } + + return IntegrationManager.hasIntegration("economy"); + } + + private static boolean isRecurringEnabled() { + EzAfk plugin = Registry.get().getPlugin(); + + if (plugin == null) { + return false; + } + + ConfigurationSection section = plugin.getConfig().getConfigurationSection("economy.cost.recurring"); + return section != null && section.getBoolean("enabled", false); + } + + private static Economy getEconomy() { + if (!IntegrationManager.hasIntegration("economy")) { + return null; + } + + Integration integration = IntegrationManager.getIntegration("economy"); + + if (integration instanceof EconomyIntegration) { + EconomyIntegration economyIntegration = (EconomyIntegration) integration; + return economyIntegration.getEconomy(); + } + + return null; + } + + private static void sendEconomyMessage(Player player, String path, String fallback, double amount, Economy economy) { + if (player == null) { + return; + } + + String formattedAmount = economy != null ? economy.format(amount) : DECIMAL_FORMAT.format(amount); + + MessageManager.sendMessage(player, path, fallback, Map.of("amount", formattedAmount)); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/IntegrationManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/IntegrationManager.java new file mode 100644 index 0000000..ea0a731 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/IntegrationManager.java @@ -0,0 +1,59 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.integration.Integration; + +import java.util.HashMap; +import java.util.Map; + +public class IntegrationManager { + private static HashMap integrations = new HashMap<>(); + // Test / runtime overrides for registration behavior. If an entry exists + // the value controls whether registration is allowed for that integration id. + private static Map registrationAllowedOverrides = new HashMap<>(); + + public static void addIntegration(String id, Integration integration) { + integrations.put(id, integration); + } + + public static Integration getIntegration(String id) { + return integrations.get(id); + } + + public static boolean hasIntegration(String id) { + Boolean override = registrationAllowedOverrides.get(id); + if (override != null && !override) { + return false; + } + return integrations.containsKey(id) && integrations.get(id).isSetup; + } + + /** + * Test/runtime hook to control whether registration of a specific integration + * id is allowed. Pass `true` to allow (default), `false` to block registration. + * This is useful for tests that need to simulate absence of optional integrations. + */ + public static void setRegistrationAllowed(String id, boolean allowed) { + registrationAllowedOverrides.put(id, allowed); + } + + public static void clearRegistrationOverride(String id) { + registrationAllowedOverrides.remove(id); + } + + public static boolean isRegistrationAllowed(String id) { + Boolean override = registrationAllowedOverrides.get(id); + return override == null ? true : override; + } + + public static void load() { + for (Integration integration : integrations.values()) { + integration.load(); + } + } + + public static void unload() { + for (Integration integration : integrations.values()) { + integration.unload(); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MessageManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MessageManager.java new file mode 100644 index 0000000..8377b4a --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MessageManager.java @@ -0,0 +1,91 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.LoreUtil; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.util.PlaceholderUtil; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Map; + +public final class MessageManager { + + private MessageManager() { + } + + public static void sendMessage(CommandSender sender, String path, String fallback) { + sendMessage(sender, path, fallback, null); + } + + public static void sendMessage(CommandSender sender, String path, String fallback, Map placeholders) { + sendMessage(sender, path, fallback, placeholders, sender instanceof Player ? (Player) sender : null); + } + + public static void sendMessage(CommandSender sender, String path, String fallback, + Map placeholders, Player playerContext) { + if (sender == null) { + return; + } + + String message = getMessage(path, fallback, placeholders, playerContext); + + if (message != null && !message.isEmpty()) { + sender.sendMessage(message); + } + } + + public static String getMessage(String path, String fallback) { + return getMessage(path, fallback, null); + } + + public static String getMessage(String path, String fallback, Map placeholders) { + return getMessage(path, fallback, placeholders, null); + } + + public static String getMessage(String path, String fallback, Map placeholders, Player playerContext) { + String message = null; + if (Registry.get().getConfigManager() != null && Registry.get().getConfigManager().getMessages() != null) { + message = Registry.get().getConfigManager().getMessages().getString(path); + } + + if (message == null) { + message = fallback; + } + + if (message == null) { + return null; + } + + if (placeholders != null) { + for (Map.Entry entry : placeholders.entrySet()) { + String value = entry.getValue() != null ? entry.getValue() : ""; + message = message.replace("%" + entry.getKey() + "%", value); + } + } + + message = applyGlobalPlaceholders(message); + + if (playerContext != null) { + message = PlaceholderUtil.resolvePlaceholderApiPlaceholders(playerContext, message, Registry.get().getLogger()); + } + + return LoreUtil.toDisplayString(message, Registry.get().getLogger()); + } + + public static String applyGlobalPlaceholders(String message) { + if (message == null || message.isEmpty()) { + return message; + } + + int afkCount = AfkState.getAfkPlayerCount(); + int activeCount = AfkState.getActivePlayerCount(); + + return message + .replace("%afk_count%", String.valueOf(afkCount)) + .replace("%afk_players%", String.valueOf(afkCount)) + .replace("%active_count%", String.valueOf(activeCount)) + .replace("%active_players%", String.valueOf(activeCount)); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MySQLManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MySQLManager.java new file mode 100644 index 0000000..505a636 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/MySQLManager.java @@ -0,0 +1,418 @@ +package com.gyvex.ezafk.manager; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import org.bukkit.configuration.file.FileConfiguration; + +import java.sql.*; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * MySQLManager provides robust, asynchronous management of all MySQL operations for EzAfk. + *

+ * Responsibilities include: + *

    + *
  • Connection lifecycle management
  • + *
  • Thread-safe, asynchronous query execution to keep the Bukkit main thread responsive
  • + *
  • Resource cleanup and error handling
  • + *
  • CRUD operations for AFK player data
  • + *
+ * All mutating queries are dispatched via a dedicated executor to ensure optimal server performance. + *

+ */ +public class MySQLManager { + private static Connection connection; + private static String host; + private static String database; + private static String username; + private static String password; + private static int port; + private static boolean enabled; + private static ExecutorService executor; + private static final Logger LOGGER = Logger.getLogger(MySQLManager.class.getName()); + + /** + * Initializes the MySQL connection and executor service using configuration values. + *

+ * Validates all required fields and logs clear errors if any are missing or invalid. + * If MySQL is disabled, ensures all resources are properly released. + *

+ */ + public static void setup() { + FileConfiguration mysqlConfig = Registry.get().getConfigManager().getMysqlConfig(); + + enabled = mysqlConfig.getBoolean("enabled"); + if (!enabled) { + // MySQL is explicitly disabled in mysql.yml; do not attempt to initialize or close resources here. + // The bootstrap/reload flows decide whether to call setup() based on configured storage type. + return; + } + + host = mysqlConfig.getString("host"); + port = mysqlConfig.getInt("port"); + database = mysqlConfig.getString("database"); + username = mysqlConfig.getString("username"); + password = mysqlConfig.getString("password"); + + if (host == null || host.isEmpty() || database == null || database.isEmpty() + || username == null || username.isEmpty() || password == null) { + LOGGER.severe("MySQL configuration is invalid. Please check host, database, username, and password fields."); + enabled = false; + return; + } + + try { + ensureExecutor(); + openConnection(); + LOGGER.info("MySQL connection established successfully."); + } catch (SQLException e) { + LOGGER.log(Level.SEVERE, "Could not connect to MySQL server!", e); + } + } + + /** + * Opens a new MySQL connection if none exists or the current one is invalid. + * Uses connection validation to avoid unnecessary reconnects. + * + * @throws SQLException if a connection cannot be established + */ + private static void openConnection() throws SQLException { + if (connection != null) { + try { + if (!connection.isClosed() && connection.isValid(2)) { + return; + } + } catch (SQLException e) { + LOGGER.log(Level.WARNING, "MySQL connection validation failed, attempting to reconnect.", e); + } + } + + synchronized (MySQLManager.class) { + if (connection == null || connection.isClosed() || !isConnectionValid(connection)) { + connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?useSSL=false&autoReconnect=true", username, password); + } + } + } + + /** + * Checks if the given MySQL connection is valid and open. + * + * @param conn the connection to check + * @return true if valid, false otherwise + */ + private static boolean isConnectionValid(Connection conn) { + try { + return conn != null && !conn.isClosed() && conn.isValid(2); + } catch (SQLException e) { + LOGGER.log(Level.WARNING, "MySQL connection validation threw an exception.", e); + return false; + } + } + + /** + * Closes the current MySQL connection, logging any errors encountered. + */ + private static void closeConnection() { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + LOGGER.log(Level.SEVERE, "Could not close MySQL connection!", e); + } + } + connection = null; + } + + /** + * Indicates whether MySQL is enabled in the configuration and available for use. + * + * @return true if MySQL is enabled, false otherwise + */ + public static boolean isEnabled() { + return enabled; + } + + /** + * Checks if the MySQL connection is currently valid and available. + * Attempts to reconnect if necessary. + * + * @return true if connected, false otherwise + */ + public static boolean isConnected() { + if (!enabled) { + closeConnection(); + return false; + } + + try { + if (connection == null || connection.isClosed() || !isConnectionValid(connection)) { + openConnection(); + } + return isConnectionValid(connection); + } catch (SQLException e) { + LOGGER.log(Level.SEVERE, "MySQL connection is not available!", e); + return false; + } + } + + /** + * Runs a database operation asynchronously on the dedicated executor. + * All database mutations are queued to avoid blocking the Bukkit main thread. + * + * @param task the operation to run + */ + private static void runAsync(Runnable task) { + if (!enabled) { + return; + } + + ExecutorService service; + + synchronized (MySQLManager.class) { + ensureExecutor(); + service = executor; + } + + if (service == null) { + LOGGER.severe("MySQL executor is not available to schedule a task."); + return; + } + + service.execute(() -> { + try { + task.run(); + } catch (Exception exception) { + LOGGER.log(Level.SEVERE, "Unexpected error during asynchronous MySQL task", exception); + } + }); + } + + /** + * Ensures the async executor is available for MySQL operations. + * Creates a new single-threaded executor if none exists or if the previous one was shut down. + */ + private static void ensureExecutor() { + synchronized (MySQLManager.class) { + if (executor != null && !executor.isShutdown() && !executor.isTerminated()) { + return; + } + + ThreadFactory threadFactory = new ThreadFactory() { + private final AtomicInteger counter = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "EzAfk-MySQL-" + counter.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + }; + + executor = Executors.newSingleThreadExecutor(threadFactory); + } + } + + /** + * Shuts down the async executor for MySQL operations, releasing all resources. + */ + private static void shutdownExecutor() { + synchronized (MySQLManager.class) { + if (executor != null) { + executor.shutdownNow(); + executor = null; + } + } + } + + /** + * Adds or updates an AFK player entry in the database, retrying once on failure. + * + * @param playerId the player's UUID + * @param lastActive the last active timestamp + */ + private static void executeAddOrUpdateAfkPlayer(UUID playerId, long lastActive) { + int attempts = 0; + while (attempts < 2) { + if (!isConnected()) { + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + continue; + } + try { + String selectQuery = "SELECT COUNT(*) FROM afk_players WHERE player_id = ?"; + try (PreparedStatement selectStatement = connection.prepareStatement(selectQuery)) { + selectStatement.setString(1, playerId.toString()); + try (ResultSet resultSet = selectStatement.executeQuery()) { + if (resultSet.next() && resultSet.getInt(1) > 0) { + String updateQuery = "UPDATE afk_players SET last_active = ? WHERE player_id = ?"; + try (PreparedStatement updateStatement = connection.prepareStatement(updateQuery)) { + updateStatement.setLong(1, lastActive); + updateStatement.setString(2, playerId.toString()); + updateStatement.executeUpdate(); + } + } else { + String insertQuery = "INSERT INTO afk_players (player_id, last_active) VALUES (?, ?)"; + try (PreparedStatement insertStatement = connection.prepareStatement(insertQuery)) { + insertStatement.setString(1, playerId.toString()); + insertStatement.setLong(2, lastActive); + insertStatement.executeUpdate(); + } + } + } + } + break; + } catch (SQLException e) { + LOGGER.log(Level.WARNING, "MySQL operation failed, retrying if possible", e); + closeConnection(); + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + } + } + } + + /** + * Removes an AFK player entry from the database, retrying once on failure. + * + * @param playerId the player's UUID + */ + private static void executeRemoveAfkPlayer(UUID playerId) { + int attempts = 0; + while (attempts < 2) { + if (!isConnected()) { + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + continue; + } + try (PreparedStatement statement = connection.prepareStatement("DELETE FROM afk_players WHERE player_id = ?")) { + statement.setString(1, playerId.toString()); + statement.executeUpdate(); + break; + } catch (SQLException e) { + LOGGER.log(Level.WARNING, "MySQL remove operation failed, retrying if possible", e); + closeConnection(); + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + } + } + } + + /** + * Updates the last active timestamp for an AFK player entry, retrying once on failure. + * + * @param playerId the player's UUID + * @param lastActive the last active timestamp + */ + private static void executeUpdateLastActive(UUID playerId, long lastActive) { + int attempts = 0; + while (attempts < 2) { + if (!isConnected()) { + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + continue; + } + try (PreparedStatement statement = connection.prepareStatement( + "UPDATE afk_players SET last_active = ? WHERE player_id = ?")) { + statement.setLong(1, lastActive); + statement.setString(2, playerId.toString()); + statement.executeUpdate(); + break; + } catch (SQLException e) { + LOGGER.log(Level.WARNING, "MySQL update operation failed, retrying if possible", e); + closeConnection(); + attempts++; + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + } + } + } + + /** + * Adds or updates the AFK player entry asynchronously. Database writes are queued and executed away from + * the Bukkit main thread for optimal performance. + */ + public static void addAfkPlayerAsync(UUID playerId, long lastActive) { + runAsync(() -> executeAddOrUpdateAfkPlayer(playerId, lastActive)); + } + + /** + * Removes the AFK player entry asynchronously. The delete operation is queued on the database executor. + */ + public static void removeAfkPlayerAsync(UUID playerId) { + runAsync(() -> executeRemoveAfkPlayer(playerId)); + } + + /** + * Updates the AFK player's last active timestamp asynchronously. + */ + public static void updateLastActiveAsync(UUID playerId, long lastActive) { + runAsync(() -> executeUpdateLastActive(playerId, lastActive)); + } + + /** + * Retrieves the last active timestamp for a player from the database. + * + * @param playerId the player's UUID + * @return the last active timestamp, or 0 if not found or on error + */ + public static long getLastActive(UUID playerId) { + long lastActive = 0; + + if (!enabled || !isConnected()) { + return lastActive; + } + + try { + String query = "SELECT last_active FROM afk_players WHERE player_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerId.toString()); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + lastActive = resultSet.getLong("last_active"); + } + } + } + } catch (SQLException e) { + LOGGER.log(Level.SEVERE, "Could not retrieve last active time from MySQL!", e); + } + + return lastActive; + } + + /** + * Checks if a player exists in the AFK database table. + * + * @param playerId the player's UUID + * @return true if the player exists, false otherwise + */ + public static boolean containsAfkPlayer(UUID playerId) { + if (!enabled || !isConnected()) { + return false; + } + + try { + String query = "SELECT 1 FROM afk_players WHERE player_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerId.toString()); + try (ResultSet resultSet = statement.executeQuery()) { + return resultSet.next(); + } + } + } catch (SQLException e) { + LOGGER.log(Level.SEVERE, "Could not check if AFK player exists in MySQL!", e); + return false; + } + } + + /** + * Shuts down the MySQL manager, closing the connection and executor, and releasing all resources. + */ + public static void shutdown() { + closeConnection(); + shutdownExecutor(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/manager/ZoneRewardStatsManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/ZoneRewardStatsManager.java new file mode 100644 index 0000000..5800895 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/manager/ZoneRewardStatsManager.java @@ -0,0 +1,126 @@ +package com.gyvex.ezafk.manager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Tracks per-player, per-zone reward statistics for the current server session. + * + *

Stats are kept in-memory only; they reset when the server restarts. All + * mutation methods are intentionally unsynchronised because they are only ever + * called from the main thread via {@link AfkZoneRewardManager}. + * + *

Retrieve stats via the typed {@link ZoneRewardStat} snapshot returned by + * {@link #getStat(UUID, String)}. + */ +public final class ZoneRewardStatsManager { + + private ZoneRewardStatsManager() {} + + /** zone-name → accumulated stats */ + private static final Map> stats = new HashMap<>(); + + /** + * Immutable snapshot of a player's rewards in one zone for the current + * session. + */ + public static final class ZoneRewardStat { + /** Total number of individual reward grants received. */ + public final long totalGrants; + /** Total currency deposited (only meaningful for the {@code economy} reward type). */ + public final double totalAmount; + + ZoneRewardStat(long totalGrants, double totalAmount) { + this.totalGrants = totalGrants; + this.totalAmount = totalAmount; + } + } + + /** Mutable stat bucket used internally. */ + private static final class MutableStat { + long totalGrants; + double totalAmount; + } + + /** + * Records a reward grant event. + * + * @param playerId UUID of the rewarded player. + * @param zoneName Name of the zone. + * @param grantCount Number of reward intervals that fired at once. + * @param amount Total amount deposited (0 for non-economy rewards). + */ + public static void recordGrant(UUID playerId, String zoneName, int grantCount, double amount) { + if (playerId == null || zoneName == null) return; + Map playerMap = stats.computeIfAbsent(playerId, k -> new HashMap<>()); + MutableStat s = playerMap.computeIfAbsent(zoneName, k -> new MutableStat()); + s.totalGrants += grantCount; + s.totalAmount += amount; + } + + /** + * Returns a stat snapshot for the given player and zone, or a zeroed + * snapshot if no data exists yet. + */ + public static ZoneRewardStat getStat(UUID playerId, String zoneName) { + if (playerId == null || zoneName == null) return new ZoneRewardStat(0, 0); + Map playerMap = stats.get(playerId); + if (playerMap == null) return new ZoneRewardStat(0, 0); + MutableStat s = playerMap.get(zoneName); + if (s == null) return new ZoneRewardStat(0, 0); + return new ZoneRewardStat(s.totalGrants, s.totalAmount); + } + + /** + * Returns an unmodifiable view of all zone stats for the given player. + * Keys are zone names; values are mutable snapshots at call time. + */ + public static Map getAllStats(UUID playerId) { + if (playerId == null) return Collections.emptyMap(); + Map playerMap = stats.get(playerId); + if (playerMap == null) return Collections.emptyMap(); + Map result = new HashMap<>(); + for (Map.Entry e : playerMap.entrySet()) { + result.put(e.getKey(), new ZoneRewardStat(e.getValue().totalGrants, e.getValue().totalAmount)); + } + return Collections.unmodifiableMap(result); + } + + /** Clears all session stats for the given player (e.g. on quit). */ + public static void clearPlayer(UUID playerId) { + stats.remove(playerId); + } + + /** Clears ALL session stats (e.g. on plugin reload/disable). */ + public static void clearAll() { + stats.clear(); + } + + /** + * Returns the total number of reward grants a player has received across + * ALL zones in the current session. + */ + public static long getTotalGrantsAllZones(UUID playerId) { + if (playerId == null) return 0; + Map playerMap = stats.get(playerId); + if (playerMap == null) return 0; + long total = 0; + for (MutableStat s : playerMap.values()) total += s.totalGrants; + return total; + } + + /** + * Returns the total amount earned by a player across all zones in the + * current session. + */ + public static double getTotalAmountAllZones(UUID playerId) { + if (playerId == null) return 0; + Map playerMap = stats.get(playerId); + if (playerMap == null) return 0; + double total = 0; + for (MutableStat s : playerMap.values()) total += s.totalAmount; + return total; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/AfkTimeModel.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/AfkTimeModel.java new file mode 100644 index 0000000..211a4dd --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/AfkTimeModel.java @@ -0,0 +1,34 @@ +package com.gyvex.ezafk.repository; + +import com.github.ezframework.jaloquent.model.Factory; +import com.github.ezframework.jaloquent.model.HasFactory; +import com.github.ezframework.jaloquent.model.Model; +import com.github.ezframework.jaloquent.model.ModelFactory; + +public final class AfkTimeModel extends Model implements HasFactory { + + public static final String TABLE_PREFIX = "afk_times"; + + public static final ModelFactory FACTORY = (id, data) -> { + AfkTimeModel m = new AfkTimeModel(id); + m.fromMap(data); + return m; + }; + + public AfkTimeModel(String id) { + super(id); + setFillable("seconds"); + } + + public static Factory factory() { + return Factory.discover(AfkTimeModel.class); + } + + public long getSeconds() { + return getAs("seconds", Long.class, 0L); + } + + public void setSeconds(long seconds) { + set("seconds", seconds); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageFactory.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageFactory.java new file mode 100644 index 0000000..4037487 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageFactory.java @@ -0,0 +1,36 @@ +package com.gyvex.ezafk.repository; + +import com.gyvex.ezafk.bootstrap.Registry; + +/** + * Factory to create a StorageRepository based on configuration. + */ +public final class StorageFactory { + private StorageFactory() {} + + public static StorageRepository create() { + String type = "yaml"; + try { + if (Registry.get() != null && Registry.get().getPlugin() != null) { + type = Registry.get().getPlugin().getConfig().getString("storage.type", "yaml"); + } + } catch (Exception ignored) {} + + if (type == null) type = "yaml"; + + switch (type.toLowerCase()) { + case "sqlite": + try { return new com.gyvex.ezafk.repository.sqlite.SQLiteStorage(); } catch (Throwable t) { Registry.get().getLogger().warning("Failed to create sqlite storage: " + t.getMessage()); } + break; + case "mysql": + try { return new com.gyvex.ezafk.repository.mysql.MySQLStorage(); } catch (Throwable t) { Registry.get().getLogger().warning("Failed to create mysql storage: " + t.getMessage()); } + break; + case "yaml": + default: + break; + } + + // Fallback to YAML storage + return new com.gyvex.ezafk.repository.yaml.YamlStorage(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageRepository.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageRepository.java new file mode 100644 index 0000000..f0ac7e6 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/StorageRepository.java @@ -0,0 +1,17 @@ +package com.gyvex.ezafk.repository; + +import java.util.Map; +import java.util.UUID; + +/** + * StorageRepository: abstraction for storing AFK-related persistent data. + */ +public interface StorageRepository { + void init() throws Exception; + Map loadAll(); + void savePlayerAfkTime(UUID player, long seconds); + void deletePlayer(UUID player); + long getPlayerAfkTime(UUID player); + void saveAll() throws Exception; + void shutdown(); +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/mysql/MySQLStorage.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/mysql/MySQLStorage.java new file mode 100644 index 0000000..8c670e7 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/mysql/MySQLStorage.java @@ -0,0 +1,155 @@ +package com.gyvex.ezafk.repository.mysql; + +import com.github.ezframework.jaloquent.config.DatabaseSettings; +import com.github.ezframework.jaloquent.config.JdbcScheme; +import com.github.ezframework.jaloquent.config.JaloquentConfig; +import com.github.ezframework.jaloquent.migration.Migration; +import com.github.ezframework.jaloquent.migration.MigrationRunner; +import com.github.ezframework.jaloquent.migration.Schema; +import com.github.ezframework.jaloquent.model.Model; +import com.github.ezframework.jaloquent.model.ModelRepository; +import com.github.ezframework.jaloquent.model.TableRegistry; +import com.github.ezframework.jaloquent.store.sql.DataSourceJdbcStore; +import com.github.ezframework.javaquerybuilder.query.sql.SqlDialect; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.repository.AfkTimeModel; +import com.gyvex.ezafk.repository.StorageRepository; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * MySQL storage backend using Jaloquent's ModelRepository with a + * DataSourceJdbcStore backed by a DriverManager JDBC connection. + */ +public class MySQLStorage implements StorageRepository { + + private static final Map TABLE_COLUMNS = Map.of( + "id", "VARCHAR(36) PRIMARY KEY", + "seconds", "BIGINT NOT NULL DEFAULT 0" + ); + + private DataSourceJdbcStore store; + private ModelRepository repo; + + @Override + public void init() throws Exception { + try { + org.bukkit.configuration.file.FileConfiguration cfg = + Registry.get().getConfigManager().getMysqlConfig(); + String host = cfg.getString("host", "localhost"); + int port = cfg.getInt("port", 3306); + String db = cfg.getString("database", "ezafk"); + String user = cfg.getString("username", "root"); + String pass = cfg.getString("password", ""); + + DatabaseSettings settings = DatabaseSettings.builder() + .jdbcScheme(JdbcScheme.MYSQL) + .host(host) + .port(port) + .databaseName(db) + .username(user) + .password(pass) + .build(); + JaloquentConfig.setDatabaseSettings(settings); + store = JaloquentConfig.buildStore(); + + new MigrationRunner(store, SqlDialect.MYSQL, List.of(new CreateAfkTimesTable())) + .run(); + + TableRegistry.register(AfkTimeModel.TABLE_PREFIX, "afk_times", TABLE_COLUMNS); + repo = new ModelRepository<>(store, AfkTimeModel.TABLE_PREFIX, AfkTimeModel.FACTORY, + SqlDialect.MYSQL); + } catch (Exception e) { + Registry.get().getLogger().warning("MySQLStorage init failed: " + e.getMessage()); + throw e; + } + } + + @Override + public Map loadAll() { + final Map result = new java.util.HashMap<>(); + try { + for (final AfkTimeModel m : repo.query(Model.queryBuilder().build())) { + try { + result.put(UUID.fromString(m.getId()), m.getSeconds()); + } catch (IllegalArgumentException ignored) {} + } + } catch (Exception e) { + Registry.get().getLogger().warning("MySQL loadAll failed: " + e.getMessage()); + } + return result; + } + + @Override + public void savePlayerAfkTime(UUID player, long seconds) { + if (player == null || store == null) return; + final AfkTimeModel model = new AfkTimeModel(player.toString()); + model.setSeconds(seconds); + try { + repo.save(model); + } catch (Exception e) { + Registry.get().getLogger().warning("MySQL save failed: " + e.getMessage()); + } + } + + @Override + public long getPlayerAfkTime(UUID player) { + if (player == null || store == null) return 0L; + try { + return repo.find(player.toString()) + .map(AfkTimeModel::getSeconds) + .orElse(0L); + } catch (Exception e) { + Registry.get().getLogger().warning("MySQL read failed: " + e.getMessage()); + return 0L; + } + } + + @Override + public void deletePlayer(UUID player) { + if (player == null || store == null) return; + try { + repo.delete(player.toString()); + } catch (Exception e) { + Registry.get().getLogger().warning("MySQL delete failed: " + e.getMessage()); + } + } + + @Override + public void saveAll() { + // Writes are immediate via repo.save(); nothing to flush. + } + + @Override + public void shutdown() { + // DataSourceJdbcStore closes connections on its own; no explicit shutdown needed. + } + + // ========================================================================= + // Inner migration class + // ========================================================================= + + private static final class CreateAfkTimesTable implements Migration { + @Override + public String getId() { return "2026_04_23_001_create_afk_times"; } + + @Override + public void up(Schema schema) throws com.github.ezframework.jaloquent.exception.MigrationException { + schema.create("afk_times", t -> t + .column("id", "VARCHAR(36) NOT NULL") + .primaryKey("id") + .column("seconds", "BIGINT NOT NULL DEFAULT 0") + .ifNotExists() + ); + } + + @Override + public void down(Schema schema) throws com.github.ezframework.jaloquent.exception.MigrationException { + schema.dropIfExists("afk_times"); + } + } +} + + diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/sqlite/SQLiteStorage.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/sqlite/SQLiteStorage.java new file mode 100644 index 0000000..47381e5 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/sqlite/SQLiteStorage.java @@ -0,0 +1,138 @@ +package com.gyvex.ezafk.repository.sqlite; + +import com.github.ezframework.jaloquent.config.DatabaseSettings; +import com.github.ezframework.jaloquent.config.JaloquentConfig; +import com.github.ezframework.jaloquent.migration.Migration; +import com.github.ezframework.jaloquent.migration.MigrationRunner; +import com.github.ezframework.jaloquent.migration.Schema; +import com.github.ezframework.jaloquent.model.Model; +import com.github.ezframework.jaloquent.model.ModelRepository; +import com.github.ezframework.jaloquent.model.TableRegistry; +import com.github.ezframework.jaloquent.store.sql.DataSourceJdbcStore; +import com.github.ezframework.javaquerybuilder.query.sql.SqlDialect; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.repository.AfkTimeModel; +import com.gyvex.ezafk.repository.StorageRepository; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * SQLite storage backend using Jaloquent's ModelRepository with a + * DataSourceJdbcStore backed by a DriverManager JDBC connection. + */ +public class SQLiteStorage implements StorageRepository { + + private static final Map TABLE_COLUMNS = Map.of( + "id", "TEXT PRIMARY KEY", + "seconds", "INTEGER NOT NULL DEFAULT 0" + ); + + private DataSourceJdbcStore store; + private ModelRepository repo; + + @Override + public void init() throws Exception { + java.io.File dataFolder = Registry.get().getPlugin().getDataFolder(); + if (!dataFolder.exists()) dataFolder.mkdirs(); + java.io.File dbFile = new java.io.File(dataFolder, "ezafk.db"); + + DatabaseSettings settings = DatabaseSettings.builder() + .url("jdbc:sqlite:" + dbFile.getAbsolutePath()) + .build(); + JaloquentConfig.setDatabaseSettings(settings); + store = JaloquentConfig.buildStore(); + + new MigrationRunner(store, SqlDialect.SQLITE, List.of(new CreateAfkTimesTable())) + .run(); + + TableRegistry.register(AfkTimeModel.TABLE_PREFIX, "afk_times", TABLE_COLUMNS); + repo = new ModelRepository<>(store, AfkTimeModel.TABLE_PREFIX, AfkTimeModel.FACTORY, + SqlDialect.SQLITE); + } + + @Override + public Map loadAll() { + final Map result = new java.util.HashMap<>(); + try { + for (final AfkTimeModel m : repo.query(Model.queryBuilder().build())) { + try { + result.put(UUID.fromString(m.getId()), m.getSeconds()); + } catch (IllegalArgumentException ignored) {} + } + } catch (Exception e) { + Registry.get().getLogger().warning("SQLite loadAll failed: " + e.getMessage()); + } + return result; + } + + @Override + public void savePlayerAfkTime(UUID player, long seconds) { + if (player == null) return; + final AfkTimeModel model = new AfkTimeModel(player.toString()); + model.setSeconds(seconds); + try { + repo.save(model); + } catch (Exception e) { + Registry.get().getLogger().warning("SQLite save failed: " + e.getMessage()); + } + } + + @Override + public void deletePlayer(UUID player) { + if (player == null || store == null) return; + try { + repo.delete(player.toString()); + } catch (Exception e) { + Registry.get().getLogger().warning("SQLite delete failed: " + e.getMessage()); + } + } + + @Override + public long getPlayerAfkTime(UUID player) { + if (player == null) return 0L; + try { + return repo.find(player.toString()) + .map(AfkTimeModel::getSeconds) + .orElse(0L); + } catch (Exception e) { + Registry.get().getLogger().warning("SQLite read failed: " + e.getMessage()); + return 0L; + } + } + + @Override + public void saveAll() { + // Writes are immediate via repo.save(); nothing to flush. + } + + @Override + public void shutdown() { + // DataSourceJdbcStore closes connections on its own; no explicit shutdown needed. + } + + // ========================================================================= + // Inner migration class + // ========================================================================= + + private static final class CreateAfkTimesTable implements Migration { + @Override + public String getId() { return "2026_04_23_001_create_afk_times"; } + + @Override + public void up(Schema schema) throws com.github.ezframework.jaloquent.exception.MigrationException { + schema.create("afk_times", t -> t + .column("id", "TEXT NOT NULL") + .primaryKey("id") + .column("seconds", "INTEGER NOT NULL DEFAULT 0") + .ifNotExists() + ); + } + + @Override + public void down(Schema schema) throws com.github.ezframework.jaloquent.exception.MigrationException { + schema.dropIfExists("afk_times"); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlDataStore.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlDataStore.java new file mode 100644 index 0000000..fbbaee1 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlDataStore.java @@ -0,0 +1,152 @@ +package com.gyvex.ezafk.repository.yaml; + +import com.github.ezframework.jaloquent.store.DataStore; +import com.github.ezframework.javaquerybuilder.query.Query; +import com.github.ezframework.javaquerybuilder.query.QueryableStorage; +import com.github.ezframework.javaquerybuilder.query.condition.ConditionEntry; +import com.github.ezframework.javaquerybuilder.query.condition.Connector; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Jaloquent DataStore backed by a Bukkit YAML file. + * + * Entries are kept in a ConcurrentHashMap (keyed by path) for fast in-memory + * access and persisted to YAML on flush(). The path format used by Jaloquent is + * {@code prefix/id}, which maps to the YAML section {@code prefix.id}. + */ +public final class YamlDataStore implements DataStore, QueryableStorage { + + private final File file; + private final Map> store = new ConcurrentHashMap<>(); + + public YamlDataStore(File file) throws IOException { + this.file = file; + if (!file.exists()) { + file.createNewFile(); + } + load(); + } + + // ========================================================================= + // DataStore + // ========================================================================= + + @Override + public void save(String path, Map data) throws Exception { + store.put(path, new LinkedHashMap<>(data)); + } + + @Override + public Optional> load(String path) throws Exception { + return Optional.ofNullable(store.get(path)); + } + + @Override + public void delete(String path) throws Exception { + store.remove(path); + } + + @Override + public boolean exists(String path) throws Exception { + return store.containsKey(path); + } + + // ========================================================================= + // QueryableStorage + // ========================================================================= + + @Override + public List query(Query q) throws Exception { + final List conditions = q.getConditions(); + final Integer rawLimit = q.getLimit(); + final Integer limit = (rawLimit != null && rawLimit >= 0) ? rawLimit : null; + + final List results = new ArrayList<>(); + for (final Map.Entry> entry : store.entrySet()) { + if (matchesConditions(entry.getValue(), conditions)) { + final String path = entry.getKey(); + final int slash = path.lastIndexOf('/'); + final String id = slash >= 0 ? path.substring(slash + 1) : path; + results.add(id); + if (limit != null && results.size() >= limit) { + break; + } + } + } + return results; + } + + // ========================================================================= + // Persistence + // ========================================================================= + + /** + * Flush all in-memory entries to the backing YAML file. + */ + public void flush() throws IOException { + final FileConfiguration config = new YamlConfiguration(); + for (final Map.Entry> pathEntry : store.entrySet()) { + final String path = pathEntry.getKey(); + final Map attrs = pathEntry.getValue(); + // "afk_times/uuid" → "afk_times.uuid" section in YAML + final String section = path.replace('/', '.'); + for (final Map.Entry attr : attrs.entrySet()) { + config.set(section + "." + attr.getKey(), attr.getValue()); + } + } + config.save(file); + } + + // ========================================================================= + // Private helpers + // ========================================================================= + + private void load() { + final FileConfiguration config = YamlConfiguration.loadConfiguration(file); + for (final String prefix : config.getKeys(false)) { + if (!config.isConfigurationSection(prefix)) continue; + final var prefixSection = config.getConfigurationSection(prefix); + if (prefixSection == null) continue; + for (final String id : prefixSection.getKeys(false)) { + if (!prefixSection.isConfigurationSection(id)) continue; + final var entrySection = prefixSection.getConfigurationSection(id); + if (entrySection == null) continue; + final Map data = new LinkedHashMap<>(); + for (final String key : entrySection.getKeys(false)) { + data.put(key, entrySection.get(key)); + } + store.put(prefix + "/" + id, data); + } + } + } + + private boolean matchesConditions(Map row, List conditions) { + if (conditions == null || conditions.isEmpty()) { + return true; + } + boolean result = false; + boolean first = true; + for (final ConditionEntry entry : conditions) { + final boolean matches = entry.getCondition().matches(row, entry.getColumn()); + if (first) { + result = matches; + first = false; + } else if (entry.getConnector() == Connector.OR) { + result = result || matches; + } else { + result = result && matches; + } + } + return result; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlStorage.java b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlStorage.java new file mode 100644 index 0000000..f0f8df7 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/repository/yaml/YamlStorage.java @@ -0,0 +1,95 @@ +package com.gyvex.ezafk.repository.yaml; + +import com.github.ezframework.jaloquent.model.Model; +import com.github.ezframework.jaloquent.model.ModelRepository; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.repository.AfkTimeModel; +import com.gyvex.ezafk.repository.StorageRepository; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class YamlStorage implements StorageRepository { + + private YamlDataStore store; + private ModelRepository repo; + + @Override + public void init() throws Exception { + File dataFolder = Registry.get().getPlugin().getDataFolder(); + if (!dataFolder.exists()) dataFolder.mkdirs(); + store = new YamlDataStore(new File(dataFolder, "afk_times.yml")); + repo = new ModelRepository<>(store, AfkTimeModel.TABLE_PREFIX, AfkTimeModel.FACTORY); + } + + @Override + public Map loadAll() { + final Map result = new HashMap<>(); + try { + for (final AfkTimeModel m : repo.query(Model.queryBuilder().build())) { + try { + result.put(UUID.fromString(m.getId()), m.getSeconds()); + } catch (IllegalArgumentException ignored) {} + } + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to load AFK times from YAML: " + e.getMessage()); + } + return result; + } + + @Override + public void savePlayerAfkTime(UUID player, long seconds) { + if (player == null) return; + final AfkTimeModel model = new AfkTimeModel(player.toString()); + model.setSeconds(seconds); + try { + repo.save(model); + } catch (Exception e) { + Registry.get().getLogger().warning("YAML save failed: " + e.getMessage()); + } + } + + @Override + public void deletePlayer(UUID player) { + if (player == null) return; + try { + repo.delete(player.toString()); + } catch (Exception e) { + Registry.get().getLogger().warning("YAML delete failed: " + e.getMessage()); + } + } + + @Override + public long getPlayerAfkTime(UUID player) { + if (player == null) return 0L; + try { + return repo.find(player.toString()) + .map(AfkTimeModel::getSeconds) + .orElse(0L); + } catch (Exception e) { + Registry.get().getLogger().warning("YAML read failed: " + e.getMessage()); + return 0L; + } + } + + @Override + public void saveAll() throws Exception { + try { + store.flush(); + } catch (IOException e) { + throw new IOException("Failed to save afk_times.yml", e); + } + } + + @Override + public void shutdown() { + try { + store.flush(); + } catch (Exception e) { + Registry.get().getLogger().warning("Failed to flush YAML storage on shutdown: " + e.getMessage()); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkActivationMode.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkActivationMode.java new file mode 100644 index 0000000..5e33205 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkActivationMode.java @@ -0,0 +1,18 @@ +package com.gyvex.ezafk.state; + +/** + * Controls how much feedback a player receives when they are marked as AFK. + */ +public enum AfkActivationMode { + /** + * Preserve the original behaviour by notifying the player and broadcasting + * the AFK change to the server. + */ + STANDARD, + + /** + * Silently mark the player as AFK without alerts, used when staff want to + * review suspicious behaviour without tipping the player off. + */ + SILENT +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkReason.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkReason.java new file mode 100644 index 0000000..2b4be88 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkReason.java @@ -0,0 +1,24 @@ +package com.gyvex.ezafk.state; + +/** + * Captures why EzAfk marked a player as AFK so staff can audit the decision. + */ +public enum AfkReason { + MANUAL("Player toggled AFK manually."), + COMMAND_FORCED("Marked AFK by a staff command."), + INACTIVITY("No recent player activity was detected."), + ANTI_INFINITE_WATER("Bypass detection: sustained water flow movement."), + ANTI_VEHICLE("Bypass detection: vehicle movement without input."), + ANTI_BUBBLE_COLUMN("Bypass detection: bubble column movement."), + OTHER("AFK status updated by the plugin."); + + private final String displayName; + + AfkReason(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkState.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkState.java new file mode 100644 index 0000000..b082bfa --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkState.java @@ -0,0 +1,414 @@ +package com.gyvex.ezafk.state; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.player.PlayerDisplayCompat; +import com.gyvex.ezafk.compatibility.sound.SoundCompat; +import com.gyvex.ezafk.integration.TabIntegration; +import com.gyvex.ezafk.manager.AfkTimeManager; +import com.gyvex.ezafk.manager.EconomyManager; +import com.gyvex.ezafk.manager.IntegrationManager; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.event.PlayerAfkStatusChangeEvent; +import com.gyvex.ezafk.state.AfkReason; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class AfkState { + public static Set afkPlayers = new HashSet<>(); + private static final Map originalDisplayNames = new HashMap<>(); + private static final Map afkStartTimes = new HashMap<>(); + private static final Map afkStatusDetails = new HashMap<>(); + private static final Map activationModes = new HashMap<>(); + private static final Set bypassedPlayers = new HashSet<>(); + + public static ToggleResult toggle(EzAfk plugin, Player player) { + return toggle(plugin, player, true, AfkReason.MANUAL, null); + } + + public static ToggleResult toggle(EzAfk plugin, Player player, boolean initiatedByPlayer) { + AfkReason reason = initiatedByPlayer ? AfkReason.MANUAL : AfkReason.INACTIVITY; + return toggle(plugin, player, initiatedByPlayer, reason, null); + } + + public static ToggleResult toggle(EzAfk plugin, Player player, boolean initiatedByPlayer, + AfkReason reason, String detail) { + if (isAfk(player.getUniqueId())) { + disable(plugin, player); + return ToggleResult.NO_LONGER_AFK; + } + + if (!EconomyManager.handleEnter(player, initiatedByPlayer)) { + return ToggleResult.FAILED; + } + + enable(plugin, player, reason, detail, AfkActivationMode.STANDARD); + return ToggleResult.NOW_AFK; + } + + public static void markAfk(EzAfk plugin, Player player, AfkReason reason, String detail, + AfkActivationMode mode) { + UUID playerId = player.getUniqueId(); + boolean alreadyAfk = isAfk(playerId); + + if (!alreadyAfk && !EconomyManager.handleEnter(player, false)) { + return; + } + + enable(plugin, player, reason, detail, mode); + } + + private static void enable(EzAfk plugin, Player player, AfkReason reason, String detail, + AfkActivationMode mode) { + UUID playerId = player.getUniqueId(); + + // Fire custom event for AFK status change (going AFK) BEFORE any state changes or messages + PlayerAfkStatusChangeEvent event = + new PlayerAfkStatusChangeEvent(player, true, reason, detail); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + afkStatusDetails.put(playerId, new AfkStatusDetails(reason, detail)); + + boolean newlyAfk = afkPlayers.add(playerId); + if (!newlyAfk) { + return; + } + + activationModes.put(playerId, mode); + afkStartTimes.put(playerId, System.currentTimeMillis()); + + if (mode == AfkActivationMode.STANDARD) { + applyAfkDisplayName(plugin, player); + + String message = getStatusMessage( + "afk.now", + "&eYou are now marked as AFK. Move around to stay active!", + player, + true + ); + if (message != null && !message.isEmpty()) { + player.sendMessage(message); + } + + boolean broadcastEnabled = plugin.getConfig().getBoolean("afk.broadcast.enabled"); + String broadcastMessage = getStatusMessage( + "afk.broadcast", + "&cPlayer&a %player% &cis now AFK", + player, + true + ); + + if (broadcastEnabled && broadcastMessage != null && !broadcastMessage.isEmpty()) { + for (Player p : Bukkit.getOnlinePlayers()) { + p.sendMessage(broadcastMessage); + } + } + + boolean titleEnabled = plugin.getConfig().getBoolean("afk.title.enabled"); + + if (titleEnabled) { + String title = MessageManager.getMessage("afk.title.title", "&eAFK", null, player); + String subtitle = MessageManager.getMessage("afk.title.subtitle", "&7You are now AFK", null, player); + + if (title == null) { + title = ""; + } + + if (subtitle == null) { + subtitle = ""; + } + + PlayerDisplayCompat.sendTitle(player, title, subtitle, 10, 70, 20); + } + + if (Registry.get().getPlugin().getConfig().getBoolean("afk.animation.enabled")) { + StateAnimator.playAfkEnableAnimation(player); + } + + if (plugin.getConfig().getBoolean("afk.hide-screen.enabled")) { + player.addPotionEffect(new PotionEffect( + PotionEffectType.BLINDNESS, + Integer.MAX_VALUE, + 0, + false, + false, + false + )); + } + } + + if (Registry.get() != null && Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().savePlayerAfkTime(playerId, LastActiveState.getLastActive(player)); + } catch (Exception ignored) {} + } + + if (mode == AfkActivationMode.STANDARD && IntegrationManager.hasIntegration("tab")) { + TabIntegration integration = (TabIntegration) IntegrationManager.getIntegration("tab"); + integration.update(); + } + } + + public static void disable(EzAfk plugin, Player player) { + UUID playerId = player.getUniqueId(); + + // Fire custom event for AFK status change (return from AFK) BEFORE any state changes or messages + PlayerAfkStatusChangeEvent event = + new PlayerAfkStatusChangeEvent(player, false, + afkStatusDetails.get(playerId) != null ? afkStatusDetails.get(playerId).reason() : null, + afkStatusDetails.get(playerId) != null ? afkStatusDetails.get(playerId).detail() : null); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + // Prevent un-AFK if cancelled + return; + } + + if (!afkPlayers.remove(playerId)) { + return; + } + + EconomyManager.onDisable(playerId); + + AfkActivationMode mode = activationModes.getOrDefault(playerId, AfkActivationMode.STANDARD); + clearAfkStart(playerId); + + restoreDisplayName(playerId); + + if (mode == AfkActivationMode.STANDARD && plugin.getConfig().getBoolean("afk.hide-screen.enabled")) { + player.removePotionEffect(PotionEffectType.BLINDNESS); + } + + if (IntegrationManager.hasIntegration("tab")) { + TabIntegration integration = (TabIntegration) IntegrationManager.getIntegration("tab"); + integration.update(); + } + + if (Registry.get() != null && Registry.get().getStorageRepository() != null) { + try { + Registry.get().getStorageRepository().deletePlayer(playerId); + } catch (Exception ignored) {} + } + + if (mode == AfkActivationMode.STANDARD && Registry.get().getPlugin().getConfig().getBoolean("unafk.animation.enabled")) { + StateAnimator.playAfkDisableAnimation(player); + } + + if (mode == AfkActivationMode.STANDARD) { + MessageManager.sendMessage(player, "afk.no-longer", "&aYou are no longer AFK!", null, player); + } + + if (mode == AfkActivationMode.STANDARD && plugin.getConfig().getBoolean("unafk.broadcast.enabled")) { + String message = getStatusMessage( + "unafk.broadcast", + "&aPlayer&7 %player% &ais no longer AFK", + player, + false + ); + + if (message != null && !message.isEmpty()) { + for (Player messagePlayer : Bukkit.getOnlinePlayers()) { + messagePlayer.sendMessage(message); + } + } + } + + boolean titleEnabled = mode == AfkActivationMode.STANDARD && plugin.getConfig().getBoolean("unafk.title.enabled"); + if (titleEnabled) { + String title = MessageManager.getMessage("unafk.title.title", "&aWelcome back!", null, player); + String subtitle = MessageManager.getMessage("unafk.title.subtitle", "&7You are no longer AFK", null, player); + + if (title == null) { + title = ""; + } + + if (subtitle == null) { + subtitle = ""; + } + + PlayerDisplayCompat.sendTitle(player, title, subtitle, 10, 70, 20); + } + + // Play a return-from-AFK sound when applicable. If Simple Voice Chat integration is available + // it will be handled by the integration listener; otherwise play a default Bukkit-compatible sound. + if (mode == AfkActivationMode.STANDARD && !IntegrationManager.hasIntegration("voicechat") + && Registry.get().getConfigManager().isUnafkSoundEnabled()) { + SoundCompat.playSound(player, 1.0f, 1.0f, "ENTITY_EXPERIENCE_ORB_PICKUP", "ORB_PICKUP", "ENTITY_ORB_PICKUP"); + } + } + + public static boolean isAfk(UUID playerId) { + return afkPlayers.contains(playerId); + } + + public static int getAfkPlayerCount() { + return afkPlayers.size(); + } + + public static int getActivePlayerCount() { + int onlineCount = Bukkit.getOnlinePlayers().size(); + int activeCount = onlineCount - getAfkPlayerCount(); + return Math.max(0, activeCount); + } + + public static long getAfkStartTime(UUID playerId) { + return afkStartTimes.getOrDefault(playerId, 0L); + } + + public static long getSecondsSinceAfk(UUID playerId) { + Long start = afkStartTimes.get(playerId); + if (start == null || start <= 0L) { + return -1L; + } + + long diff = System.currentTimeMillis() - start; + if (diff < 0L) { + return 0L; + } + + return diff / 1000L; + } + + public static long getTotalAfkSeconds(UUID playerId) { + return AfkTimeManager.getTotalAfkSeconds(playerId); + } + + public static AfkStatusDetails getAfkStatusDetails(UUID playerId) { + return afkStatusDetails.get(playerId); + } + + public static void clearAfkStart(UUID playerId) { + Long startTime = afkStartTimes.remove(playerId); + if (startTime != null) { + AfkTimeManager.recordAfkSession(playerId, startTime, System.currentTimeMillis()); + } + afkStatusDetails.remove(playerId); + activationModes.remove(playerId); + } + + public static boolean toggleBypass(UUID playerId) { + if (bypassedPlayers.contains(playerId)) { + bypassedPlayers.remove(playerId); + return false; + } + + bypassedPlayers.add(playerId); + return true; + } + + public static boolean isBypassed(UUID playerId) { + return bypassedPlayers.contains(playerId); + } + + public static void clearBypass() { + bypassedPlayers.clear(); + } + + public static void restoreAllDisplayNames() { + Map snapshot = new HashMap<>(originalDisplayNames); + for (Map.Entry entry : snapshot.entrySet()) { + UUID uuid = entry.getKey(); + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + player.setDisplayName(entry.getValue()); + } + } + originalDisplayNames.clear(); + afkStartTimes.clear(); + afkStatusDetails.clear(); + activationModes.clear(); + } + + public static void forgetDisplayName(UUID playerId) { + originalDisplayNames.remove(playerId); + clearAfkStart(playerId); + } + + public static Map getActiveAfkSessions() { + return new HashMap<>(afkStartTimes); + } + + private static String getStatusMessage(String path, String fallback, Player player, boolean useDisplayNameForPlayerPlaceholder) { + Map placeholders = new HashMap<>(); + + if (player != null) { + String replacement = useDisplayNameForPlayerPlaceholder ? player.getDisplayName() : player.getName(); + + if (replacement == null || replacement.isEmpty()) { + replacement = player.getName(); + } + + if (replacement == null) { + replacement = ""; + } + + placeholders.put("player", replacement); + } + + return MessageManager.getMessage(path, fallback, placeholders, player); + } + + private static void applyAfkDisplayName(EzAfk plugin, Player player) { + if (!plugin.getConfig().getBoolean("afk.display-name.enabled")) { + return; + } + + UUID uuid = player.getUniqueId(); + String baseName = originalDisplayNames.computeIfAbsent(uuid, id -> { + String currentDisplayName = player.getDisplayName(); + if (currentDisplayName == null || currentDisplayName.isEmpty()) { + return player.getName(); + } + return currentDisplayName; + }); + + if (baseName == null || baseName.isEmpty()) { + baseName = player.getName(); + } + + String prefix = plugin.getConfig().getString("afk.display-name.prefix", "&7[AFK] "); + String suffix = plugin.getConfig().getString("afk.display-name.suffix", ""); + String format = plugin.getConfig().getString("afk.display-name.format", "%prefix%%player%%suffix%"); + + if (prefix == null) { + prefix = ""; + } + if (suffix == null) { + suffix = ""; + } + if (format == null || format.isEmpty()) { + format = "%prefix%%player%%suffix%"; + } + + String combined = format + .replace("%prefix%", prefix) + .replace("%player%", baseName) + .replace("%suffix%", suffix); + combined = ChatColor.translateAlternateColorCodes('&', combined); + + player.setDisplayName(combined); + } + + private static void restoreDisplayName(UUID playerId) { + String original = originalDisplayNames.remove(playerId); + if (original == null) { + return; + } + + Player player = Bukkit.getPlayer(playerId); + if (player != null) { + player.setDisplayName(original); + } + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkStatusDetails.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkStatusDetails.java new file mode 100644 index 0000000..6ac5999 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/AfkStatusDetails.java @@ -0,0 +1,34 @@ +package com.gyvex.ezafk.state; + +/** + * Stores metadata for why a player is currently considered AFK. + */ +public final class AfkStatusDetails { + private final AfkReason reason; + private final String detail; + + public AfkStatusDetails(AfkReason reason, String detail) { + this.reason = (reason == null) ? AfkReason.OTHER : reason; + this.detail = detail; + } + + public AfkReason reason() { + return reason; + } + + public String detail() { + return detail; + } + + public String getReasonDisplayName() { + return reason.getDisplayName(); + } + + public boolean hasDetail() { + return detail != null && !detail.isBlank(); + } + + public String getDetail() { + return detail; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/LastActiveState.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/LastActiveState.java new file mode 100644 index 0000000..9e0c6f3 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/LastActiveState.java @@ -0,0 +1,34 @@ +package com.gyvex.ezafk.state; + +import com.gyvex.ezafk.manager.EconomyManager; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.UUID; + +public class LastActiveState { + public static HashMap lastActive = new HashMap<>(); + + public static void update(Player player) { + lastActive.put(player.getUniqueId(), System.currentTimeMillis()); + EconomyManager.onActivity(player); + } + + public static long getLastActive(Player player) { + return getLastActive(player.getUniqueId()); + } + + public static long getLastActive(UUID playerId) { + return lastActive.getOrDefault(playerId, System.currentTimeMillis()); + } + + public static long getSecondsSinceLastActive(UUID playerId) { + long last = getLastActive(playerId); + long diff = System.currentTimeMillis() - last; + if (diff < 0) { + return 0L; + } + + return diff / 1000L; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/StateAnimator.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/StateAnimator.java new file mode 100644 index 0000000..df96012 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/StateAnimator.java @@ -0,0 +1,48 @@ +package com.gyvex.ezafk.state; + +import org.bukkit.Material; +import org.bukkit.Particle; +import com.gyvex.ezafk.compatibility.sound.SoundCompat; +import com.gyvex.ezafk.compatibility.ParticleCompat; +import org.bukkit.entity.Player; + +public class StateAnimator { + public static void playAfkEnableAnimation(Player player) { + Particle happyVillager = ParticleCompat.getHappyVillager(); + + if (happyVillager != null) { + player.getWorld().spawnParticle(happyVillager, player.getLocation(), 20, 1, 1, 1, 0.1); + } + + Particle blockParticle = ParticleCompat.getBlock(); + if (blockParticle != null) { + try { + // createBlockData() is available on MC 1.13+; guarded here as a safety net. + Object blockData = Material.OAK_LOG.getClass().getMethod("createBlockData").invoke(Material.OAK_LOG); + player.getWorld().spawnParticle(blockParticle, player.getLocation(), 30, 1, 1, 1, 1, blockData); + } catch (Exception ignored) { + player.getWorld().spawnParticle(blockParticle, player.getLocation(), 30, 1, 1, 1, 1); + } + } + + Particle cherryLeaves = ParticleCompat.getCherryLeaves(); + + if (cherryLeaves != null) { + player.getWorld().spawnParticle(cherryLeaves, player.getLocation().add(0, 2, 0), 20, 1, 1, 1, 0.1); + } + + SoundCompat.playSound(player, 1.0f, 1.0f, "BLOCK_WOOD_BREAK", "WOOD_BREAK", "DIG_WOOD"); + SoundCompat.playSound(player, 0.5f, 1.5f, "ENTITY_EXPERIENCE_ORB_PICKUP", "ORB_PICKUP", "ENTITY_ORB_PICKUP"); + } + + public static void playAfkDisableAnimation(Player player) { + Particle smoke = ParticleCompat.getSmoke(); + + if (smoke != null) { + player.getWorld().spawnParticle(smoke, player.getLocation(), 20, 1, 1, 1, 0.1); + } + + SoundCompat.playSound(player, 1.0f, 1.0f, "BLOCK_GRASS_BREAK", "DIG_GRASS", "STEP_GRASS"); + SoundCompat.playSound(player, 0.5f, 0.5f, "BLOCK_WOOD_HIT", "WOOD_CLICK", "STEP_WOOD"); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/state/ToggleResult.java b/ezafk-core/src/main/java/com/gyvex/ezafk/state/ToggleResult.java new file mode 100644 index 0000000..2933d28 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/state/ToggleResult.java @@ -0,0 +1,13 @@ +package com.gyvex.ezafk.state; + +/** + * Represents the outcome of attempting to toggle a player's AFK state. + */ +public enum ToggleResult { + /** The player has been marked as AFK. */ + NOW_AFK, + /** The player is no longer marked as AFK. */ + NO_LONGER_AFK, + /** The AFK state was not changed (for example due to economy restrictions). */ + FAILED +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/task/AfkCheckTask.java b/ezafk-core/src/main/java/com/gyvex/ezafk/task/AfkCheckTask.java new file mode 100644 index 0000000..b2051dd --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/task/AfkCheckTask.java @@ -0,0 +1,331 @@ +package com.gyvex.ezafk.task; + +import com.gyvex.ezafk.EzAfk; +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.player.PlayerDisplayCompat; +import com.gyvex.ezafk.compatibility.player.PlayerKickCompat; +import com.gyvex.ezafk.integration.Integration; +import com.gyvex.ezafk.integration.WorldGuardIntegration; +import com.gyvex.ezafk.integration.ezcountdown.EzCountdownIntegration; +import com.gyvex.ezafk.integration.worldguard.flag.AfkBypassFlag; +import com.gyvex.ezafk.manager.AfkZoneManager; +import com.gyvex.ezafk.zone.Zone; +import com.gyvex.ezafk.manager.EconomyManager; +import com.gyvex.ezafk.manager.IntegrationManager; +import com.gyvex.ezafk.manager.MessageManager; +import com.gyvex.ezafk.manager.AfkZoneRewardManager; +import com.gyvex.ezafk.state.AfkReason; +import com.gyvex.ezafk.state.AfkState; +import com.gyvex.ezafk.state.LastActiveState; +import com.gyvex.ezafk.util.DurationFormatter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.Locale; + +public class AfkCheckTask extends BukkitRunnable { + // Tracks which warnings have been sent to each player: Map> + private static final Map> warnedPlayers = new HashMap<>(); + + @Override + public void run() { + EzAfk plugin = Registry.get().getPlugin(); + long kickTimeoutMs = plugin.getConfig().getLong("kick.timeout") * 1000; + long afkTimeoutMs = plugin.getConfig().getLong("afk.timeout") * 1000; + boolean kickEnabled = plugin.getConfig().getBoolean("kick.enabled"); + boolean kickEnabledWhenFull = plugin.getConfig().getBoolean("kick.enabledWhenFull"); + long currentTime = System.currentTimeMillis(); + + // Warning configuration + boolean warningsEnabled = plugin.getConfig().getBoolean("kick.warnings.enabled", true); + List warningIntervals = plugin.getConfig().getIntegerList("kick.warnings.intervals"); + List warningDisplays = resolveDisplays(plugin); + boolean warningDebug = plugin.getConfig().getBoolean("kick.warnings.debug", false); + + for (Player player : Bukkit.getOnlinePlayers()) { + UUID playerId = player.getUniqueId(); + long lastActive = LastActiveState.getLastActive(player); + + if (shouldBypassAfkCheck(player, playerId)) { + continue; + } + + // Config-defined AFK zones: only bypass AFK checks if zone exists and rewards are disabled + Zone zone = AfkZoneManager.getZoneForPlayer(player); + if (zone != null && !zone.rewardEnabled) { + continue; + } + + if (shouldBypassWorldGuard(player)) { + continue; + } + + handleWarnings(player, playerId, lastActive, currentTime, afkTimeoutMs, kickTimeoutMs, + warningsEnabled, warningIntervals, warningDisplays, warningDebug); + + if (shouldKick(player, playerId, lastActive, currentTime, afkTimeoutMs, kickTimeoutMs, kickEnabled, kickEnabledWhenFull)) { + kickPlayer(player, playerId); + } else if (shouldMarkAfk(player, playerId, lastActive, currentTime, afkTimeoutMs)) { + markPlayerAfk(player, playerId, lastActive, afkTimeoutMs, currentTime); + } + } + + EconomyManager.processRecurringCharges(); + AfkZoneRewardManager.processRewards(); + } + + private boolean shouldBypassAfkCheck(Player player, UUID playerId) { + // Blacklist always wins — explicitly prevents bypass even for ops + if (com.gyvex.ezafk.manager.BypassListManager.isBlacklisted(playerId)) return false; + // Whitelist always grants bypass regardless of config/permission + if (com.gyvex.ezafk.manager.BypassListManager.isWhitelisted(playerId)) return true; + // Standard config-based bypass + EzAfk plugin = Registry.get().getPlugin(); + return plugin.getConfig().getBoolean("afk.bypass.enabled") + && (player.hasPermission("ezafk.bypass") || AfkState.isBypassed(playerId)); + } + + private boolean shouldBypassWorldGuard(Player player) { + Object flag = AfkBypassFlag.get(); + if (flag == null) return false; + + try { + Class worldGuardClass = Class.forName("com.sk89q.worldguard.WorldGuard"); + Object wgInstance = worldGuardClass.getMethod("getInstance").invoke(null); + Object platform = wgInstance.getClass().getMethod("getPlatform").invoke(wgInstance); + Object container = platform.getClass().getMethod("getRegionContainer").invoke(platform); + + Class bukkitAdapterClass = Class.forName("com.sk89q.worldedit.bukkit.BukkitAdapter"); + Object adaptedWorld = bukkitAdapterClass.getMethod("adapt", org.bukkit.World.class).invoke(null, player.getWorld()); + + Object regions = container.getClass().getMethod("get", adaptedWorld.getClass()).invoke(container, adaptedWorld); + if (regions == null) return false; + + Object blockVector = bukkitAdapterClass.getMethod("asBlockVector", org.bukkit.Location.class).invoke(null, player.getLocation()); + Object set = regions.getClass().getMethod("getApplicableRegions", blockVector.getClass()).invoke(regions, blockVector); + + for (Object region : (Iterable) set) { + try { + Object flagsMap = region.getClass().getMethod("getFlags").invoke(region); + boolean contains = (boolean) flagsMap.getClass().getMethod("containsKey", Object.class).invoke(flagsMap, flag); + if (contains) { + Object flagVal = region.getClass().getMethod("getFlag", flag.getClass()).invoke(region, flag); + Class stateEnum = Class.forName("com.sk89q.worldguard.protection.flags.StateFlag$State"); + Object allow = java.lang.Enum.valueOf((Class) stateEnum, "ALLOW"); + if (allow.equals(flagVal)) { + return true; + } + } + } catch (NoSuchMethodException nsme) { + // API mismatch; ignore this region and continue + } + } + } catch (Throwable ignored) { + // WorldGuard not available or error while checking regions. + return false; + } + + return false; + } + + /** + * Resolves the list of display types to use for kick warnings. + * + *

If {@code kick.warnings.displays} is set in the config it is used + * directly (upper-cased). Otherwise the legacy {@code kick.warnings.mode} + * value is translated: {@code chat} → [CHAT], {@code title} → [TITLE], + * {@code both} → [CHAT, TITLE]. + */ + private static List resolveDisplays(EzAfk plugin) { + List configured = plugin.getConfig().getStringList("kick.warnings.displays"); + if (!configured.isEmpty()) { + List result = new ArrayList<>(configured.size()); + for (String d : configured) { + result.add(d.toUpperCase(Locale.ROOT)); + } + return result; + } + // Backward-compat: translate legacy mode key + String mode = plugin.getConfig().getString("kick.warnings.mode", "both"); + switch (mode.toLowerCase(Locale.ROOT)) { + case "chat": return Collections.singletonList("CHAT"); + case "title": return Collections.singletonList("TITLE"); + default: return Arrays.asList("CHAT", "TITLE"); + } + } + + private void handleWarnings(Player player, UUID playerId, long lastActive, long currentTime, + long afkTimeoutMs, long kickTimeoutMs, boolean warningsEnabled, + List warningIntervals, List warningDisplays, boolean warningDebug) { + EzAfk plugin = Registry.get().getPlugin(); + boolean kickEnabled = plugin.getConfig().getBoolean("kick.enabled"); + if (!kickEnabled) { + return; + } + long timeAfk = currentTime - lastActive - afkTimeoutMs; + if (!warningsEnabled || !AfkState.isAfk(playerId) || timeAfk < 0 || timeAfk >= kickTimeoutMs) { + if (warningDebug) { + Registry.get().getLogger().info("[EzAfk][Debug] Skipping warnings: enabled=" + warningsEnabled + ", isAfk=" + AfkState.isAfk(playerId) + ", timeAfk=" + timeAfk + ", kickTimeoutMs=" + kickTimeoutMs); + } + return; + } + + long secondsUntilKick = (kickTimeoutMs - timeAfk) / 1000L; + Set sent = warnedPlayers.computeIfAbsent(playerId, k -> new HashSet<>()); + if (warningDebug) { + Registry.get().getLogger().info("[EzAfk][Debug] Player " + player.getName() + " secondsUntilKick=" + secondsUntilKick + ", warningIntervals=" + warningIntervals + ", sent=" + sent); + } + for (int interval : warningIntervals) { + if (secondsUntilKick == interval && !sent.contains(interval)) { + if (warningDebug) { + Registry.get().getLogger().info("[EzAfk][Debug] Sending warning to " + player.getName() + " for interval " + interval + "s"); + } + sendWarning(player, interval, warningDisplays); + sent.add(interval); + } + } + } + + private void sendWarning(Player player, int seconds, List displays) { + Map placeholders = new HashMap<>(); + placeholders.put("seconds", String.valueOf(seconds)); + + // CHAT is always sent directly through MessageManager regardless of whether + // EzCountdown is active, because EzCountdown's countdown format is not + // suitable for a localised one-shot chat sentence. + if (displays.contains("CHAT")) { + MessageManager.sendMessage(player, "kick.warning.chat", + "&eYou will be kicked for being AFK in &c%seconds% &eseconds!", placeholders); + } + + // Non-CHAT displays: delegate to EzCountdown when available so the + // countdown ticks live and SCOREBOARD / DIALOG are supported. + // If EzCountdown is absent or disabled, fall back to native Bukkit APIs. + List visualDisplays = new ArrayList<>(displays); + visualDisplays.remove("CHAT"); + if (visualDisplays.isEmpty()) return; + + EzCountdownIntegration ez = getEzCountdownIntegration(); + if (ez != null) { + String msg = MessageManager.getMessage( + "kick.warning.countdown", + "&cAFK kick in &e{seconds}s", + placeholders); + if (msg == null) msg = "&cAFK kick in &e{seconds}s"; + if (!ez.sendKickWarning(player, msg, seconds, visualDisplays)) { + // EzCountdown rejected the request; fall back to native display. + sendWarningNative(player, seconds, visualDisplays, placeholders); + } + } else { + sendWarningNative(player, seconds, visualDisplays, placeholders); + } + } + + /** + * Native (non-EzCountdown) fallback for visual kick-warning displays. + * Handles TITLE, ACTION_BAR, and BOSS_BAR via {@link PlayerDisplayCompat}. + */ + private void sendWarningNative(Player player, int seconds, List displays, + Map placeholders) { + for (String display : displays) { + switch (display) { + case "TITLE": { + String title = MessageManager.getMessage("kick.warning.title.title", "&cAFK Warning", placeholders); + String subtitle = MessageManager.getMessage("kick.warning.title.subtitle", "&eKicked in &c%seconds% &esec!", placeholders); + PlayerDisplayCompat.sendTitle(player, title, subtitle, 10, 40, 10); + break; + } + case "ACTION_BAR": { + String msg = MessageManager.getMessage("kick.warning.action_bar", + "&eKicked in &c%seconds%s &efor being AFK!", placeholders); + PlayerDisplayCompat.sendActionBar(player, msg); + break; + } + case "BOSS_BAR": { + String msg = MessageManager.getMessage("kick.warning.boss_bar", + "&cAFK Warning &e\u2014 kicked in &c%seconds%s", placeholders); + PlayerDisplayCompat.showBossBarWarning(player, msg, seconds); + break; + } + case "SCOREBOARD": + case "DIALOG": + Registry.get().getLogger().warning( + "[EzAfk] Display type '" + display + "' requires the EzCountdown plugin. " + + "Install EzCountdown or remove this type from kick.warnings.displays."); + break; + default: + Registry.get().getLogger().warning( + "[EzAfk] Unknown kick warning display type: '" + display + "'. " + + "Supported without EzCountdown: CHAT, TITLE, ACTION_BAR, BOSS_BAR."); + break; + } + } + } + + /** + * Removes any active kick-warning display for a player — both the native + * BossBar (if one was created via {@link PlayerDisplayCompat}) and any + * EzCountdown countdown that was delegated to this integration. + */ + public static void removeBossBar(UUID playerId) { + PlayerDisplayCompat.removeWarningBossBar(playerId); + EzCountdownIntegration ez = getEzCountdownIntegration(); + if (ez != null) { + ez.removeKickWarning(playerId); + } + } + + /** + * Returns the active {@link EzCountdownIntegration} if it is set up, + * or {@code null} if EzCountdown is not available or disabled. + */ + private static EzCountdownIntegration getEzCountdownIntegration() { + if (!IntegrationManager.hasIntegration("ezcountdown")) return null; + Integration integration = IntegrationManager.getIntegration("ezcountdown"); + if (!(integration instanceof EzCountdownIntegration)) return null; + EzCountdownIntegration ez = (EzCountdownIntegration) integration; + return ez.isSetup ? ez : null; + } + + private boolean shouldKick(Player player, UUID playerId, long lastActive, long currentTime, + long afkTimeoutMs, long kickTimeoutMs, + boolean kickEnabled, boolean kickEnabledWhenFull) { + boolean timeoutExceeded = currentTime - lastActive > (kickTimeoutMs + afkTimeoutMs); + boolean canKick = kickEnabled + || (kickEnabledWhenFull && Bukkit.getOnlinePlayers().size() == Bukkit.getMaxPlayers()); + return timeoutExceeded && canKick && AfkState.isAfk(playerId); + } + + private void kickPlayer(Player player, UUID playerId) { + String message = MessageManager.getMessage( + "kick.message", + "&cYou have been kicked from this server for being AFK too long!" + ); + if (message == null) { + message = ""; + } + PlayerKickCompat.kickPlayer(player, message); + LastActiveState.lastActive.remove(playerId); + warnedPlayers.remove(playerId); // Reset warnings after kick + removeBossBar(playerId); // Remove any active boss bar + } + + private boolean shouldMarkAfk(Player player, UUID playerId, long lastActive, long currentTime, long afkTimeoutMs) { + return currentTime - lastActive > afkTimeoutMs + && !AfkState.isAfk(playerId) + && !EconomyManager.isEconomyBlocked(player); + } + + private void markPlayerAfk(Player player, UUID playerId, long lastActive, long afkTimeoutMs, long currentTime) { + long inactivitySeconds = (currentTime - lastActive) / 1000L; + long timeoutSeconds = afkTimeoutMs / 1000L; + String detail = "Inactive for " + DurationFormatter.formatDuration(inactivitySeconds) + + " (threshold " + DurationFormatter.formatDuration(timeoutSeconds) + ")"; + AfkState.toggle(Registry.get().getPlugin(), player, false, AfkReason.INACTIVITY, detail); + warnedPlayers.remove(playerId); // Reset warnings if player returns from AFK + removeBossBar(playerId); // Remove any active boss bar + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/task/TaskManager.java b/ezafk-core/src/main/java/com/gyvex/ezafk/task/TaskManager.java new file mode 100644 index 0000000..710b929 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/task/TaskManager.java @@ -0,0 +1,20 @@ +package com.gyvex.ezafk.task; + +import com.gyvex.ezafk.bootstrap.Registry; +import com.gyvex.ezafk.compatibility.scheduler.TaskHandle; +import org.bukkit.plugin.java.JavaPlugin; + +public class TaskManager { + private TaskHandle afkCheckTask; + + public void startAfkCheckTask(JavaPlugin plugin) { + afkCheckTask = Registry.get().getScheduler().runTaskTimer(new AfkCheckTask(), 20, 20); + } + + public void cancelTasks() { + if (afkCheckTask != null) { + afkCheckTask.cancel(); + afkCheckTask = null; + } + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/util/CommandUtil.java b/ezafk-core/src/main/java/com/gyvex/ezafk/util/CommandUtil.java new file mode 100644 index 0000000..0d79818 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/util/CommandUtil.java @@ -0,0 +1,28 @@ +package com.gyvex.ezafk.util; + +import com.gyvex.ezafk.manager.MessageManager; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class CommandUtil { + /** + * Checks if the sender has the required permission. Sends a message and returns false if not. + */ + public static boolean checkPermission(CommandSender sender, String permission, String messageKey, String defaultMsg) { + if (!sender.hasPermission(permission)) { + MessageManager.sendMessage(sender, messageKey, defaultMsg); + return false; + } + return true; + } + + /** + * Finds an online or offline player by name. + */ + public static OfflinePlayer findPlayer(String name) { + Player online = Bukkit.getPlayer(name); + return online != null ? online : Bukkit.getOfflinePlayer(name); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/util/DurationFormatter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/util/DurationFormatter.java new file mode 100644 index 0000000..223d362 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/util/DurationFormatter.java @@ -0,0 +1,42 @@ +package com.gyvex.ezafk.util; + +import java.time.Duration; + +public final class DurationFormatter { + private DurationFormatter() { + } + + public static String formatDuration(long seconds) { + if (seconds < 0) { + seconds = 0; + } + + Duration duration = Duration.ofSeconds(seconds); + + long days = duration.toDays(); + duration = duration.minusDays(days); + long hours = duration.toHours(); + duration = duration.minusHours(hours); + long minutes = duration.toMinutes(); + duration = duration.minusMinutes(minutes); + long remainingSeconds = duration.getSeconds(); + + StringBuilder builder = new StringBuilder(); + + if (days > 0) { + builder.append(days).append("d "); + } + + if (hours > 0 || builder.length() > 0) { + builder.append(hours).append("h "); + } + + if (minutes > 0 || builder.length() > 0) { + builder.append(minutes).append("m "); + } + + builder.append(remainingSeconds).append("s"); + + return builder.toString().trim(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/util/PlaceholderUtil.java b/ezafk-core/src/main/java/com/gyvex/ezafk/util/PlaceholderUtil.java new file mode 100644 index 0000000..37c74b2 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/util/PlaceholderUtil.java @@ -0,0 +1,154 @@ +package com.gyvex.ezafk.util; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class PlaceholderUtil { + private static boolean placeholderAPIAvailable = false; + private static boolean checkedAvailability = false; + private static final Map placeholderCache = new ConcurrentHashMap<>(); + + public static String replacePlaceholders(Player executor, Player target, String message) { + if (message == null) { + return null; + } + String result = message; + result = result.replace("%executor%", executor != null ? executor.getName() : ""); + result = result.replace("%player%", target != null ? target.getName() : ""); + return result; + } + + public static String colorize(String message) { + if (message == null) { + return null; + } + return ChatColor.translateAlternateColorCodes('&', message); + } + + /** + * Checks if PlaceholderAPI is available on the server. + * @param logger Logger for info/warning messages (may be null) + * @return true if PlaceholderAPI is present and enabled + */ + public static boolean isPlaceholderAPIAvailable(Logger logger) { + if (!checkedAvailability) { + try { + Class.forName("me.clip.placeholderapi.PlaceholderAPI"); + Plugin papi = Bukkit.getPluginManager().getPlugin("PlaceholderAPI"); + placeholderAPIAvailable = papi != null && papi.isEnabled(); + + if (logger != null) { + if (placeholderAPIAvailable) { + logger.info("PlaceholderAPI found and enabled. Placeholder support is available."); + } else { + logger.info("PlaceholderAPI not found. Placeholder resolution will use fallback behavior."); + } + } + } catch (Exception e) { + placeholderAPIAvailable = false; + if (logger != null) { + logger.info("PlaceholderAPI not available. Placeholder resolution will use fallback behavior."); + } + } + checkedAvailability = true; + } + return placeholderAPIAvailable; + } + + /** + * Resolves placeholders in the given text using PlaceholderAPI if available. + * Falls back to local replacements when PAPI is not present. + * + * @param player The player context for placeholder resolution + * @param text The text containing placeholders to resolve + * @param logger Logger for debug messages + * @return The text with placeholders resolved, or original text if unavailable + */ + public static String resolvePlaceholders(Player player, String text, Logger logger) { + if (text == null) { + return null; + } + + // First apply local replacements (%executor%, %player%) + String replaced = replacePlaceholders(player, null, text); + + if (!isPlaceholderAPIAvailable(logger)) { + return replaced; + } + + String cacheKey = (player != null ? player.getUniqueId().toString() : "null") + ":" + replaced; + String cached = placeholderCache.get(cacheKey); + if (cached != null) { + return cached; + } + + try { + String resolved = me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, replaced); + placeholderCache.put(cacheKey, resolved); + return resolved; + } catch (Exception e) { + if (logger != null) { + logger.warning("Failed to resolve placeholders via PlaceholderAPI: " + e.getMessage()); + } + return replaced; + } + } + + /** + * Resolves PlaceholderAPI placeholders using the provided player context without applying + * EzAfk's local %player% or %executor% substitutions. + */ + public static String resolvePlaceholderApiPlaceholders(Player player, String text, Logger logger) { + if (text == null) { + return null; + } + + if (!isPlaceholderAPIAvailable(logger)) { + return text; + } + + try { + return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, text); + } catch (Exception e) { + if (logger != null) { + logger.warning("Failed to resolve placeholders via PlaceholderAPI: " + e.getMessage()); + } + return text; + } + } + + /** + * Resolves placeholders without player context (server-wide/offline placeholders). + */ + public static String resolvePlaceholders(String text, Logger logger) { + if (text == null) { + return null; + } + if (!isPlaceholderAPIAvailable(logger)) { + return text; + } + try { + return me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(null, text); + } catch (Exception e) { + if (logger != null) { + logger.warning("Failed to resolve placeholders via PlaceholderAPI: " + e.getMessage()); + } + return text; + } + } + + /** + * Resets the availability check (useful for testing). + */ + public static void resetAvailabilityCheck() { + checkedAvailability = false; + placeholderAPIAvailable = false; + placeholderCache.clear(); + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/version/FallbackVersionAdapter.java b/ezafk-core/src/main/java/com/gyvex/ezafk/version/FallbackVersionAdapter.java new file mode 100644 index 0000000..d685732 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/version/FallbackVersionAdapter.java @@ -0,0 +1,51 @@ +package com.gyvex.ezafk.version; + +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.potion.PotionEffectType; + +import java.util.logging.Logger; + +final class FallbackVersionAdapter implements VersionAdapter { + + private final Attribute fallbackAttribute; + private final Logger logger = Logger.getLogger("EzAfk"); + + FallbackVersionAdapter() { + Attribute attr = null; + try { + attr = Attribute.valueOf("GENERIC_MAX_HEALTH"); + } catch (Exception e) { + try { + attr = Attribute.valueOf("MAX_HEALTH"); + } catch (Exception ignored) {} + } + fallbackAttribute = attr; + if (fallbackAttribute == null) { + logger.warning("[EzAfk] Could not resolve MAX_HEALTH attribute"); + } + } + + @Override + public Attribute getMaxHealthAttribute() { + return fallbackAttribute; + } + + @Override + public PotionEffectType getEffect(String minecraftKey) { + try { + return PotionEffectType.getByKey(org.bukkit.NamespacedKey.minecraft(minecraftKey)); + } catch (Exception ignored) { + return null; + } + } + + @Override + public Enchantment getEnchantment(String minecraftKey) { + try { + return Enchantment.getByKey(org.bukkit.NamespacedKey.minecraft(minecraftKey)); + } catch (Exception ignored) { + return null; + } + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/version/VersionAdapterFactory.java b/ezafk-core/src/main/java/com/gyvex/ezafk/version/VersionAdapterFactory.java new file mode 100644 index 0000000..bcb30e0 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/version/VersionAdapterFactory.java @@ -0,0 +1,57 @@ +package com.gyvex.ezafk.version; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.util.logging.Logger; + +public final class VersionAdapterFactory { + + private VersionAdapterFactory() {} + + public static String detectMcVersion() { + String version = Bukkit.getVersion(); + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("MC: (\\d+\\.\\d+\\.\\d+)").matcher(version); + if (matcher.find()) { + return matcher.group(1); + } + matcher = java.util.regex.Pattern.compile("(\\d+\\.\\d+\\.\\d+)").matcher(version); + if (matcher.find()) { + return matcher.group(1); + } + return "unknown"; + } + + public static boolean isRegistryAccessSupported() { + String version = detectMcVersion(); + if ("unknown".equals(version)) { + return false; + } + try { + String[] parts = version.split("\\."); + int major = Integer.parseInt(parts[0]); + int minor = Integer.parseInt(parts[1]); + int patch = Integer.parseInt(parts[2]); + return major > 1 || (major == 1 && minor > 21) || (major == 1 && minor == 21 && patch >= 1); + } catch (Exception e) { + return false; + } + } + + public static VersionAdapter create(Plugin plugin, Logger logger) { + if (isRegistryAccessSupported()) { + try { + Class clazz = VersionAdapterFactory.class.getClassLoader() + .loadClass("com.gyvex.ezafk.version.Paper261PlusVersionAdapter"); + java.lang.reflect.Constructor ctor = clazz.getDeclaredConstructor(); + return (VersionAdapter) ctor.newInstance(); + } catch (ClassNotFoundException e) { + logger.warning("[EzAfk] Paper261PlusVersionAdapter not found, using fallback"); + } catch (Exception e) { + logger.warning("[EzAfk] Failed to load Paper261PlusVersionAdapter: " + e.getMessage()); + } + } + + return new FallbackVersionAdapter(); + } +} \ No newline at end of file diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/zone/Zone.java b/ezafk-core/src/main/java/com/gyvex/ezafk/zone/Zone.java new file mode 100644 index 0000000..7f9a44d --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/zone/Zone.java @@ -0,0 +1,82 @@ +package com.gyvex.ezafk.zone; + +import org.bukkit.entity.Player; + +import java.util.List; + +public final class Zone { + public final String name; + public final String world; + public final double minX, minY, minZ, maxX, maxY, maxZ; + public final boolean rewardEnabled; + public final long rewardIntervalSeconds; + public final int rewardMaxStack; // -1 for unlimited + public final double rewardAmount; + public final String rewardType; // economy | command | item + public final String rewardCommand; // for command type + public final String rewardItemMaterial; // for item type + public final int rewardItemAmount; + public final int rewardLimit; // per-player limit + public final long rewardLimitCooldownSeconds; + /** Whether EzCountdown reward countdown notification is enabled for this zone. */ + public final boolean notificationEnabled; + /** + * EzCountdown display types for the reward countdown notification + * (e.g. ACTION_BAR, TITLE, BOSS_BAR). An empty list disables EzCountdown + * and falls back to the regular chat/action-bar grant message. + */ + public final List notificationDisplays; + /** + * Message shown in the EzCountdown countdown. Supports EzCountdown's live + * {seconds} / {formatted} placeholders and EzAfk's %zone% / %amount%. + */ + public final String notificationMessage; + /** + * How many seconds the countdown should run. Defaults to + * {@code rewardIntervalSeconds} when ≤ 0, so the countdown ticks from + * the interval duration down to zero and the player knows exactly when + * the next reward lands. + */ + public final int notificationDuration; + + public Zone(String name, String world, double minX, double minY, double minZ, double maxX, double maxY, double maxZ, + boolean rewardEnabled, long rewardIntervalSeconds, int rewardMaxStack, double rewardAmount, + String rewardType, String rewardCommand, String rewardItemMaterial, int rewardItemAmount, + int rewardLimit, long rewardLimitCooldownSeconds, + boolean notificationEnabled, List notificationDisplays, + String notificationMessage, int notificationDuration) { + this.name = name == null ? "" : name; + this.world = world; + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + this.rewardEnabled = rewardEnabled; + this.rewardIntervalSeconds = rewardIntervalSeconds; + this.rewardMaxStack = rewardMaxStack; + this.rewardAmount = rewardAmount; + this.rewardType = rewardType == null ? "economy" : rewardType; + this.rewardCommand = rewardCommand; + this.rewardItemMaterial = rewardItemMaterial; + this.rewardItemAmount = Math.max(1, rewardItemAmount); + this.rewardLimit = rewardLimit; + this.rewardLimitCooldownSeconds = rewardLimitCooldownSeconds; + this.notificationEnabled = notificationEnabled; + this.notificationDisplays = notificationDisplays == null ? List.of() : List.copyOf(notificationDisplays); + this.notificationMessage = notificationMessage == null ? "&7Next reward in &e{seconds}s &7in &a%zone%" : notificationMessage; + this.notificationDuration = notificationDuration; + } + + public boolean contains(Player player) { + if (!player.getWorld().getName().equals(this.world)) return false; + double x = player.getLocation().getX(); + double y = player.getLocation().getY(); + double z = player.getLocation().getZ(); + + return x >= minX && x <= maxX + && y >= minY && y <= maxY + && z >= minZ && z <= maxZ; + } +} diff --git a/ezafk-core/src/main/java/com/gyvex/ezafk/zone/ZoneCache.java b/ezafk-core/src/main/java/com/gyvex/ezafk/zone/ZoneCache.java new file mode 100644 index 0000000..2480872 --- /dev/null +++ b/ezafk-core/src/main/java/com/gyvex/ezafk/zone/ZoneCache.java @@ -0,0 +1,44 @@ +package com.gyvex.ezafk.zone; + +import org.bukkit.Location; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.UUID; + +public class ZoneCache { + public static final Map zonePos1 = new ConcurrentHashMap<>(); + public static final Map zonePos2 = new ConcurrentHashMap<>(); + public static final Map zonePos1Time = new ConcurrentHashMap<>(); + public static final Map zonePos2Time = new ConcurrentHashMap<>(); + + public static void clearPositions(UUID playerId) { + zonePos1.remove(playerId); + zonePos2.remove(playerId); + zonePos1Time.remove(playerId); + zonePos2Time.remove(playerId); + } + + public static void cleanupExpiredPositions(long expiryMillis) { + long now = System.currentTimeMillis(); + List toRemove = new java.util.ArrayList<>(); + + for (Map.Entry e : zonePos1Time.entrySet()) { + if (now - e.getValue() > expiryMillis) toRemove.add(e.getKey()); + } + for (UUID id : toRemove) { + zonePos1.remove(id); + zonePos1Time.remove(id); + } + + toRemove.clear(); + for (Map.Entry e : zonePos2Time.entrySet()) { + if (now - e.getValue() > expiryMillis) toRemove.add(e.getKey()); + } + for (UUID id : toRemove) { + zonePos2.remove(id); + zonePos2Time.remove(id); + } + } +} diff --git a/ezafk-core/src/main/resources/config.yml b/ezafk-core/src/main/resources/config.yml new file mode 100644 index 0000000..8efebcd --- /dev/null +++ b/ezafk-core/src/main/resources/config.yml @@ -0,0 +1,210 @@ +# Config for EzAfk 2.0.0 +# GUI settings have moved to gui.yml. +# MySQL settings have moved to mysql.yml. +# MySQL settings have moved to mysql.yml. + +# Enable debug logging (e.g. Jaloquent storage save/delete messages). Default: false +debug: false + +# Storage backend selection for AFK persistence. Valid values: yaml, sqlite, mysql +storage: + type: yaml + # Interval in seconds between asynchronous flushes of AFK time data to disk. + # Lower values write more frequently while higher values reduce disk activity. + flush-interval-seconds: 30 + +messages: + # Default language for the generated messages file. Supported values: en, es, nl, ru, zh + # Language files are located in the messages/ directory. + language: en +afk: + # Timeout in seconds + timeout: 300 + # Enabling this will activate the function of the permission "ezafk.bypass" + # By default OP players have this permission. + bypass: + enabled: true + broadcast: + # Enable broadcast message to all online players when player is AFK + enabled: true + # Placeholders: + # %player% - Display name of player + # %afk_count% - Current number of players marked as AFK + # %active_count% - Current number of online players not marked as AFK + title: + enabled: true + hide-screen: + # Apply a blindness effect to AFK players to hide their screen until they return. + # Requires MC 1.13+ (PotionEffect icon parameter). Available from the minimum supported version. + enabled: false + animation: + # Spawns particles and plays sounds when a player goes/returns from AFK. + # Cherry Leaves particles are only available on MC 1.20+; they are silently skipped on older versions. + enabled: true + # Prevent players from bypassing AFK + anti: + # Toggle the infinite water flow protection + infinite-waterflow: false + infinite-vehicle: false + # Detects upward push from soul-sand bubble columns. Requires MC 1.13+ (BUBBLE_COLUMN material). + # Available from the minimum supported version. + bubble-column: false + # When true, bypass attempts silently mark the player as AFK instead of alerting them. + flag-only: false + sound: + # Plays an MP3 file when a player goes AFK via Simple Voice Chat. + # Only active when the Simple Voice Chat plugin is present and integration.voicechat is true/auto. + # Disabled by default; enable only after installing Simple Voice Chat. + enabled: false + # Path to the MP3 file to play (relative to plugin folder). Must be a valid MP3 file, 48kHz recommended. + file: "mp3/ezafk-sound.mp3" + # Change the in-game display name (e.g., chat) while the player is AFK. + display-name: + enabled: false + prefix: "&7[AFK] " + suffix: "" + format: "%prefix%%player%%suffix%" +unafk: + broadcast: + # Broadcast a message when a player is not longer AFK + enabled: true + # Placeholders: + # %player% - Display name of player + # %afk_count% - Current number of players marked as AFK + # %active_count% - Current number of online players not marked as AFK + title: + enabled: true + animation: + enabled: true + sound: + # Plays an MP3 file when a player returns from AFK via Simple Voice Chat. + # Only active when the Simple Voice Chat plugin is present and integration.voicechat is true/auto. + # Disabled by default; enable only after installing Simple Voice Chat. + enabled: false + # Path to the MP3 file to play when returning from AFK (relative to plugin folder) + file: "mp3/ezafk-sound.mp3" +economy: + # Enable economy-based costs for going AFK. Requires Vault with an economy provider installed. + enabled: false + # Players with this permission bypass all AFK costs. + bypass-permission: "ezafk.economy.bypass" + # Players inside a WorldGuard region with the afk-bypass flag will also skip costs. + cost: + enter: + # Charge a player when they are marked as AFK (either manually or automatically). + enabled: true + amount: 25.0 + # When true, the AFK toggle is cancelled if the player cannot afford the cost. + # When false, the charge is skipped and the player still becomes AFK. + require-funds: true + # Seconds to wait before attempting to automatically toggle a player AFK again after a failed charge. + retry-delay: 60 + recurring: + # Continuously charge AFK players while they remain AFK. + enabled: false + amount: 5.0 + # Time between recurring charges in seconds. + interval: 300 + # When true, the player must have enough funds for the charge to succeed. + # Failing to pay while this is true always removes their AFK state. + require-funds: true + # Controls optional charges (when require-funds is false): true removes the AFK state on failure, false retries later. + kick-on-fail: false +kick: + # Enable the kick function after being AFK for x amount of time + enabled: false + # Enable the kick function when the lobby is full + enabledWhenFull: false + # Timeout in seconds + timeout: 600 + # Warning system before kicking for AFK + warnings: + enabled: true + # Enable debug logging for kick warnings (default: false) + debug: false + # List of warning intervals (in seconds before kick) to send warnings at. + # Example: [60, 30, 10] will warn at 60s, 30s, and 10s before kick. + intervals: [60, 30, 10] + # How to send warnings. Use 'displays' (recommended) to choose one or more + # display types. When 'displays' is empty the legacy 'mode' value is used. + # + # Available display types (all servers): + # CHAT – chat message (always via EzAfk's MessageManager) + # TITLE – title / subtitle overlay + # ACTION_BAR – action-bar text above the hotbar + # BOSS_BAR – boss-bar at the top of the screen + # + # Additional display types (requires EzCountdown plugin): + # SCOREBOARD – sidebar scoreboard countdown + # DIALOG – in-game dialog popup + # + # https://modrinth.com/plugin/ezcountdown + # When EzCountdown is installed and its integration is active, all non-CHAT + # display types are delegated to EzCountdown so the countdown ticks in real + # time. Without EzCountdown, TITLE / ACTION_BAR / BOSS_BAR use Bukkit APIs + # directly, and SCOREBOARD / DIALOG are ignored with a warning in the log. + # CHAT is always handled by EzAfk's own message system. + # + # Examples: + # displays: [CHAT] # chat message only + # displays: [TITLE] # title/subtitle only + # displays: [CHAT, TITLE] # equivalent to mode: both + # displays: [ACTION_BAR] # action-bar only + # displays: [CHAT, ACTION_BAR, BOSS_BAR] # three at once + # displays: [BOSS_BAR, SCOREBOARD] # EzCountdown required for SCOREBOARD + displays: [] + # Legacy fallback when 'displays' is empty. + # Options: chat | title | both + mode: both + # Messages are configurable in messages.yml under kick.warning + # Warnings are only sent once per interval per AFK session. + # If a player returns from AFK or is kicked, the warning state resets. +integration: + # https://dev.bukkit.org/projects/worldguard + # Automatically skipped when WorldGuard is not installed. + # Enable WorldEdit integration. When true, EzAfk will attempt to read + # WorldEdit selections when creating AFK zones. + worldedit: true + worldguard: true + # https://www.spigotmc.org/resources/tab-1-5-1-21.57806/ + tab: true + # https://www.spigotmc.org/resources/playtime-%C2%BB-1-17-1-21-10.44852/ + # When enabled, EzAfk registers additional placeholders that subtract tracked AFK + # time from the configured Playtime placeholder value. + # EXPERIMENTAL through PlaceholderAPI integration + playtime: + enabled: false + # Placeholder that resolves to the player's total playtime **in seconds**. + # The default matches the Playtime plugin's %playtime_time_total_seconds% placeholder. + placeholder: "%playtime_time_total_seconds%" + # Check for EzAfk updates while startup server + # This is being done async and will not affect the startup time of your server + spigot: + check-for-update: true + voicechat: auto # Options: true, false, auto. 'auto' will detect if Simple Voice Chat is available + # EzCountdown integration for richer kick-warning display types. + # When 'auto', EzAfk enables the integration if EzCountdown is installed. + # Options: true, false, auto + ezcountdown: auto + # Controls automatic WorldGuard flag registration for AFK BYPASS. + # Options: + # auto - Try to register the flag during EzAfk load. If the WorldGuard registry + # is already locked, EzAfk will log concise guidance for admins. + # manual - Do not attempt automatic registration; admin must register the flag manually + # or adjust plugin load order. Use this if you prefer manual control. + flag-registration: auto + # TAB integration + # Requires: https://www.spigotmc.org/resources/tab-1-5-1-21.57806/ + tab-prefix: + enabled: false + # Strategy for applying AFK names when the TAB plugin is installed. + # Options: + # auto - Use TAB when available, otherwise fall back to EzAfk's custom list handling. + # tab - Require TAB for name changes. Falls back to the custom implementation if the plugin is missing. + # custom - Always use EzAfk's built-in implementation, even if TAB is installed. + mode: auto + prefix: "&7[AFK] " + # Text to append to the end of the player's name while AFK. + suffix: "" + # Full format for the displayed name. Available placeholders: %prefix%, %player%, %suffix% + format: "%prefix%%player%%suffix%" diff --git a/ezafk-core/src/main/resources/gui.yml b/ezafk-core/src/main/resources/gui.yml new file mode 100644 index 0000000..7e27f21 --- /dev/null +++ b/ezafk-core/src/main/resources/gui.yml @@ -0,0 +1,49 @@ +# GUI configuration for EzAfk's player actions menu. +# Supported action types: +# - KICK: Kick the AFK player using the optional target-message. +# - MESSAGE: Send the optional target-message to the AFK player. +# - TELEPORT: Teleport the staff member to the AFK player. +# - COMMAND: Run the configured command as console (supports placeholders). +# Available placeholders: +# %player% - Name of the AFK player the GUI was opened for. +# %executor% - Name of the player who clicked the GUI item. +# Total number of slots must be a multiple of 9 and between 9 and 54. +inventory-size: 9 +actions: + kick: + slot: 0 + material: IRON_BOOTS + display-name: "&cKick Player" + lore: + - "&7Remove the player from the server." + type: KICK + target-message: "&cYou were kicked for being AFK too long." + feedback-message: "&aSuccessfully kicked %player%" + alert: + slot: 1 + material: PAPER + display-name: "&eSend Alert" + lore: + - "&7Send a private message to the player." + type: MESSAGE + target-message: "&eYou are marked as AFK. Keep active to prevent getting kicked!" + feedback-message: "&aSent alert to %player%" + teleport: + slot: 2 + material: COMPASS + display-name: "&aTeleport to Player" + lore: + - "&7Teleport to the player's location." + type: TELEPORT + feedback-message: "&aTeleported to %player%" + +empty-slot-filler: + enabled: true + material: GRAY_STAINED_GLASS_PANE + display-name: " " + lore: [] + +back-button: + display-name: "&6« Back to Overview" + lore: + - "&7Return to the player list." diff --git a/ezafk-core/src/main/resources/messages/messages.yml b/ezafk-core/src/main/resources/messages/messages.yml new file mode 100644 index 0000000..989c290 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages.yml @@ -0,0 +1,142 @@ +anti: + vehicle: "&cYou can't toggle AFK while riding in a vehicle." + infinite-waterflow: "&cWater flow tricks won't bypass AFK detection." + bubble-column: "&cYou cannot use bubble columns to avoid AFK detection." +afk: + now: "&eYou are now marked as AFK. Move around to stay active!" + broadcast: "&cPlayer&a %player% &cis now AFK" + no-longer: "&aWelcome back! You're no longer AFK." + title: + title: "&eAFK" + subtitle: "&7You are now AFK" +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cOnly in-game players can manage their own AFK status." + failed: "&cYour AFK status could not be updated." + no-permission: "&cYou don't have permission to toggle another player's AFK state." + now-afk: "&a%target% is now marked as AFK." + no-longer-afk: "&a%target% is no longer marked as AFK." + failed: "&c%target%'s AFK status could not be changed." + reload: + no-permission: "&cYou don't have permission to reload the configuration." + success: "&aConfiguration reloaded successfully." + gui: + players-only: "&cOnly players in-game can open the AFK GUI." + no-permission: "&cYou don't have permission to open the AFK GUI." + bypass: + no-permission: "&cYou don't have permission to change AFK bypass status." + enabled: "&a%target% will now bypass AFK detection." + enabled-target: "&aAFK detection is now bypassed for you." + disabled: "&a%target% will no longer bypass AFK detection." + disabled-target: "&cAFK detection is no longer bypassed for you." + whitelist: + header: "&6Bypass Whitelist:" + entry: "&e- &f%player%" + empty: "&eThe bypass whitelist is empty." + added: "&a%player% has been added to the bypass whitelist." + added-target: "&aYou have been added to the AFK bypass whitelist." + removed: "&a%player% has been removed from the bypass whitelist." + removed-target: "&cYou have been removed from the AFK bypass whitelist." + already: "&e%player% is already on the bypass whitelist." + not-found: "&e%player% is not on the bypass whitelist." + blacklist: + header: "&6Bypass Blacklist:" + entry: "&e- &f%player%" + empty: "&eThe bypass blacklist is empty." + added: "&a%player% has been added to the bypass blacklist." + added-target: "&cYou have been added to the AFK bypass blacklist and will now be subject to AFK detection." + removed: "&a%player% has been removed from the bypass blacklist." + removed-target: "&aYou have been removed from the AFK bypass blacklist." + already: "&e%player% is already on the bypass blacklist." + not-found: "&e%player% is not on the bypass blacklist." + info: + no-permission: "&cYou don't have permission to view AFK reports." + not-afk: "&e%target% is not currently marked as AFK." + header: "&6AFK report for &e%player%&6:" + reason: "&7Reason: &f%reason%" + detail: "&7Details: &f%detail%" + no-detail: "&7Details: &fNo additional details were recorded." + duration: "&7AFK for: &f%duration%" + last-activity: "&7Last activity: &f%last% ago" + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cThat player couldn't be found." +economy: + enter: + charged: "&eYou paid %amount% to go AFK." + insufficient: "&cYou need at least %amount% to go AFK." + failed: "&cWe couldn't charge your account to go AFK." + skipped: "&eThe AFK entry fee was skipped because your account couldn't be charged." + recurring: + charged: "&eYou paid %amount% to remain AFK." + revoked-insufficient: "&cYou don't have enough funds to remain AFK, so your AFK status has been removed." + revoked-failed: "&cWe couldn't charge your account to remain AFK, so your AFK status has been removed." + skipped: "&eThe recurring AFK fee was skipped because your account couldn't be charged. We'll try again later." + failed: "&cWe couldn't charge your account to remain AFK. We'll try again later." +gui: + overview: + active: + no-permission: "&cYou don't have permission to view active players." + actions: + no-permission: "&cYou don't have permission to use player actions." + error: + player-not-found: "&cWe couldn't find the player linked to that action." + no-command: "&cNo command has been configured for this action." + default-kick-message: "&cYou've been kicked for staying AFK too long." +unafk: + broadcast: "&aPlayer&7 %player% &ais no longer AFK" + title: + title: "&aWelcome back!" + subtitle: "&7You are no longer AFK" +kick: + message: "&cYou have been kicked from this server for being AFK too long!" + warning: + chat: "&eYou will be kicked for being AFK in &c%seconds% &eseconds!" + title: + title: "&cAFK Warning" + subtitle: "&eKicked in &c%seconds% &esec!" + action_bar: "&eKicked in &c%seconds%s &efor being AFK!" + boss_bar: "&cAFK Warning &e\u2014 kicked in &c%seconds%s" + # Message used when EzCountdown handles the countdown display (TITLE, ACTION_BAR, + # BOSS_BAR, SCOREBOARD, DIALOG). Supports EzCountdown placeholders: + # {seconds} – live remaining seconds (updated every tick) + # {minutes} – remaining minutes + # {formatted} – formatted duration (e.g. "1m 30s") + countdown: "&cAFK kick in &e{seconds}s" + + +afkzone: + enter: "&aYou have entered the AFK zone &e%zone%&a." + exit: "&7You have left the AFK zone &e%zone%&7." + reward: + granted: + economy: "&aYou received %amount% for staying AFK in %zone%." + command: "&aYou received a command reward for staying AFK in %zone%." + item: "&aYou received %count%x %item% for staying AFK in %zone%." + default: "&aYou received a reward for staying AFK in %zone%." + limit: + reached: "&cYou have reached your reward limit for %reward_name%. Come back in %time_remaining%." + cooldown: "&cYou have reached your reward limit for %reward_name%. Come back in %time_remaining%." + failed: "&cAFK zone reward failed for %zone%: %reason%" + + add: + selection-missing: "&cNo WorldEdit selection found - please select a region or provide explicit coordinates." + coords-required: "&cWorldEdit not available. Provide coordinates: /afk zone add " + coords-invalid: "&cInvalid coordinates provided. Use numbers." + pos1: + set: "&aPosition 1 set. Use /afk zone pos2 to set the second corner." + pos2: + set: "&aPosition 2 set. Use /afk zone add to create the zone." + pos-cleared-self: "&aCleared your stored positions." + pos-cleared-target: "&aCleared stored positions for %player%." diff --git a/ezafk-core/src/main/resources/messages/messages_de.yml b/ezafk-core/src/main/resources/messages/messages_de.yml new file mode 100644 index 0000000..b249e88 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_de.yml @@ -0,0 +1,108 @@ +anti: + vehicle: "&cDu kannst AFK nicht umschalten, während du in einem Fahrzeug sitzt." + infinite-waterflow: "&cWasserfluss-Tricks umgehen die AFK-Erkennung nicht." + bubble-column: "&cDu kannst Blasen-Säulen nicht verwenden, um die AFK-Erkennung zu umgehen." +afk: + now: "&eDu bist jetzt als AFK markiert. Bewege dich, um aktiv zu bleiben!" + broadcast: "&cSpieler&a %player% &cist jetzt AFK" + no-longer: "&aWillkommen zurück! Du bist nicht mehr AFK." + title: + title: "&eAFK" + subtitle: "&7Du bist jetzt AFK" +command: + usage: "&cVerwendung: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cNur Spieler im Spiel können ihren eigenen AFK-Status verwalten." + no-permission: "&cDu hast keine Berechtigung, den AFK-Status eines anderen Spielers zu ändern." + now-afk: "&a%target% ist jetzt als AFK markiert." + no-longer-afk: "&a%target% ist nicht mehr AFK." + reload: + no-permission: "&cDu hast keine Berechtigung, die Konfiguration neu zu laden." + success: "&aKonfiguration erfolgreich neu geladen." + gui: + players-only: "&cNur Spieler können das AFK-GUI öffnen." + no-permission: "&cDu hast keine Berechtigung, das AFK-GUI zu öffnen." + bypass: + no-permission: "&cDu hast keine Berechtigung, den AFK-Bypass zu ändern." + enabled: "&a%target% wird jetzt die AFK-Erkennung umgehen." + enabled-target: "&aDu wirst jetzt die AFK-Erkennung umgehen." + disabled: "&a%target% umgeht die AFK-Erkennung nun nicht mehr." + disabled-target: "&cDu umgehst die AFK-Erkennung nicht mehr." + info: + no-permission: "&cDu hast keine Berechtigung, AFK-Berichte anzusehen." + not-afk: "&e%target% ist derzeit nicht als AFK markiert." + header: "&6AFK-Bericht für &e%player%&6:" + reason: "&7Grund: &f%reason%" + detail: "&7Details: &f%detail%" + no-detail: "&7Details: &fEs wurden keine zusätzlichen Details aufgezeichnet." + duration: "&7AFK-Dauer: &f%duration%" + last-activity: "&7Letzte Aktivität: &fvor %last%" + time: + players-only: "&cNur Spieler können ihre eigene AFK-Zeit ansehen." + no-permission: "&cDu hast keine Berechtigung, deine AFK-Zeit anzusehen." + others-no-permission: "&cDu hast keine Berechtigung, die AFK-Zeit anderer Spieler anzusehen." + self: "&aDu warst insgesamt %duration% AFK." + other: "&a%player% war insgesamt %duration% AFK." + top: + no-permission: "&cDu hast keine Berechtigung, die AFK-Rangliste anzusehen." + empty: "&eEs wurde noch keine AFK-Zeit aufgezeichnet." + header: "&6Top AFK-Spieler:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cDer Spieler konnte nicht gefunden werden." +economy: + enter: + charged: "&eDu hast %amount% bezahlt, um AFK zu gehen." + insufficient: "&cDu musst mindestens %amount% haben, um AFK zu gehen." + failed: "&cWir konnten dein Konto nicht belasten, um AFK zu gehen." + skipped: "&eDie Gebühr für das AFK-Gehen wurde übersprungen, da dein Konto nicht belastet werden konnte." + recurring: + charged: "&eDir wurden %amount% berechnet, um AFK zu bleiben." + revoked-insufficient: "&cDu hast nicht genug Geld, um AFK zu bleiben. Dein AFK-Status wurde entfernt." + revoked-failed: "&cDie Belastung schlug fehl, dein AFK-Status wurde entfernt." + skipped: "&eDie wiederkehrende Gebühr wurde übersprungen. Wir versuchen es später erneut." + failed: "&cWir konnten dein Konto nicht belasten. Wir versuchen es später erneut." +gui: + overview: + active: + no-permission: "&cDu hast keine Berechtigung, aktive Spieler anzuzeigen." + actions: + no-permission: "&cDu hast keine Berechtigung, Spieleraktionen zu verwenden." + error: + player-not-found: "&cWir konnten den mit dieser Aktion verknüpften Spieler nicht finden." + no-command: "&cFür diese Aktion wurde kein Befehl konfiguriert." + default-kick-message: "&cDu wurdest gekickt, weil du zu lange AFK warst." +unafk: + broadcast: "&aSpieler&7 %player% &aist nicht mehr AFK" + title: + title: "&aWillkommen zurück!" + subtitle: "&7Du bist nicht mehr AFK" +kick: + message: "&cDu wurdest wegen zu langer AFK-Zeit vom Server gekickt!" + +afkzone: + reward: + granted: + economy: "&aDu hast %amount% erhalten, weil du in %zone% AFK warst." + command: "&aDu hast einen Befehl als Belohnung erhalten, weil du in %zone% AFK warst." + item: "&aDu hast %count%x %item% erhalten, weil du in %zone% AFK warst." + default: "&aDu hast eine Belohnung erhalten, weil du in %zone% AFK warst." + limit: + reached: "&cDu hast das Limit von %reward_limit% für %reward_name% erreicht. Komm in %time_remaining% wieder." + cooldown: "&cDu hast das Limit von %reward_limit% für %reward_name% erreicht. Komm in %time_remaining% wieder." + + add: + selection-missing: "&cKeine WorldEdit-Auswahl gefunden - wähle eine Region oder gib Koordinaten an." + coords-required: "&cWorldEdit ist nicht verfügbar. Gib Koordinaten an: /afk zone add " + coords-invalid: "&cUngültige Koordinaten. Verwende Zahlen." + pos1: + set: "&aPosition 1 gesetzt. Verwende /afk zone pos2, um die zweite Ecke zu setzen." + pos2: + set: "&aPosition 2 gesetzt. Verwende /afk zone add , um die Zone zu erstellen." + pos-cleared-self: "&aDeine gespeicherten Positionen wurden gelöscht." + pos-cleared-target: "&aGespeicherte Positionen für %player% wurden gelöscht." + + reset: + success: "&aBelohnungszähler für %player% zurückgesetzt." + zone: + success: "&aBelohnungszähler für %player% in Zone %zone% zurückgesetzt." diff --git a/ezafk-core/src/main/resources/messages/messages_en.yml b/ezafk-core/src/main/resources/messages/messages_en.yml new file mode 100644 index 0000000..4ba08b6 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_en.yml @@ -0,0 +1,109 @@ +anti: + vehicle: "&cYou can't toggle AFK while riding in a vehicle." + infinite-waterflow: "&cWater flow tricks won't bypass AFK detection." + bubble-column: "&cYou cannot use bubble columns to avoid AFK detection." +afk: + now: "&eYou are now marked as AFK. Move around to stay active!" + broadcast: "&cPlayer&a %player% &cis now AFK" + no-longer: "&aWelcome back! You're no longer AFK." + title: + title: "&eAFK" + subtitle: "&7You are now AFK" +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cOnly in-game players can manage their own AFK status." + no-permission: "&cYou don't have permission to toggle another player's AFK state." + now-afk: "&a%target% is now marked as AFK." + no-longer-afk: "&a%target% is no longer marked as AFK." + reload: + no-permission: "&cYou don't have permission to reload the configuration." + success: "&aConfiguration reloaded successfully." + gui: + players-only: "&cOnly players in-game can open the AFK GUI." + no-permission: "&cYou don't have permission to open the AFK GUI." + bypass: + no-permission: "&cYou don't have permission to change AFK bypass status." + enabled: "&a%target% will now bypass AFK detection." + enabled-target: "&aAFK detection is now bypassed for you." + disabled: "&a%target% will no longer bypass AFK detection." + disabled-target: "&cAFK detection is no longer bypassed for you." + info: + no-permission: "&cYou don't have permission to view AFK reports." + not-afk: "&e%target% is not currently marked as AFK." + header: "&6AFK report for &e%player%&6:" + reason: "&7Reason: &f%reason%" + detail: "&7Details: &f%detail%" + no-detail: "&7Details: &fNo additional details were recorded." + duration: "&7AFK for: &f%duration%" + last-activity: "&7Last activity: &f%last% ago" + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cThat player couldn't be found." +economy: + enter: + charged: "&eYou paid %amount% to go AFK." + insufficient: "&cYou need at least %amount% to go AFK." + failed: "&cWe couldn't charge your account to go AFK." + skipped: "&eThe AFK entry fee was skipped because your account couldn't be charged." + recurring: + charged: "&eYou paid %amount% to remain AFK." + revoked-insufficient: "&cYou don't have enough funds to remain AFK, so your AFK status has been removed." + revoked-failed: "&cWe couldn't charge your account to remain AFK, so your AFK status has been removed." + skipped: "&eThe recurring AFK fee was skipped because your account couldn't be charged. We'll try again later." + failed: "&cWe couldn't charge your account to remain AFK. We'll try again later." +gui: + overview: + active: + no-permission: "&cYou don't have permission to view active players." + actions: + no-permission: "&cYou don't have permission to use player actions." + error: + player-not-found: "&cWe couldn't find the player linked to that action." + no-command: "&cNo command has been configured for this action." + default-kick-message: "&cYou've been kicked for staying AFK too long." +unafk: + broadcast: "&aPlayer&7 %player% &ais no longer AFK" + title: + title: "&aWelcome back!" + subtitle: "&7You are no longer AFK" +kick: + message: "&cYou have been kicked from this server for being AFK too long!" + + +afkzone: + reward: + granted: + economy: "&aYou received %amount% for staying AFK in %zone%." + command: "&aYou received a command reward for staying AFK in %zone%." + item: "&aYou received %count%x %item% for staying AFK in %zone%." + default: "&aYou received a reward for staying AFK in %zone%." + limit: + reached: "&cYou already reached the %reward_limit% for %reward_name%, come back in %time_remaining%." + cooldown: "&cYou already reached the %reward_limit% for %reward_name%, come back in %time_remaining%." + + add: + selection-missing: "&cNo WorldEdit selection found - please select a region or provide explicit coordinates." + coords-required: "&cWorldEdit not available. Provide coordinates: /afk zone add " + coords-invalid: "&cInvalid coordinates provided. Use numbers." + pos1: + set: "&aPosition 1 set. Use /afk zone pos2 to set the second corner." + pos2: + set: "&aPosition 2 set. Use /afk zone add to create the zone." + pos-cleared-self: "&aCleared your stored positions." + pos-cleared-target: "&aCleared stored positions for %player%." + + reset: + success: "&aReset reward counts for %player%." + zone: + success: "&aReset reward counts for %player% in zone %zone%." diff --git a/ezafk-core/src/main/resources/messages/messages_es.yml b/ezafk-core/src/main/resources/messages/messages_es.yml new file mode 100644 index 0000000..97689b3 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_es.yml @@ -0,0 +1,98 @@ +anti: + vehicle: "&cNo puedes alternar AFK mientras vas en un vehículo." + infinite-waterflow: "&cLos trucos con el flujo de agua no evitarán la detección AFK." + bubble-column: "&cNo puedes usar columnas de burbujas para evitar la detección AFK." +afk: + now: "&eAhora estás marcado como AFK. ¡Muévete para permanecer activo!" + broadcast: "&cEl jugador&a %player% &cahora está AFK" + no-longer: "&a¡Bienvenido de nuevo! Ya no estás AFK." + title: + title: "&eAFK" + subtitle: "&7Ahora estás AFK" +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cSolo los jugadores en el juego pueden gestionar su propio estado AFK." + no-permission: "&cNo tienes permiso para cambiar el estado AFK de otro jugador." + now-afk: "&a%target% ahora está marcado como AFK." + no-longer-afk: "&a%target% ya no está marcado como AFK." + reload: + no-permission: "&cNo tienes permiso para recargar la configuración." + success: "&aConfiguración recargada correctamente." + gui: + players-only: "&cSolo los jugadores en el juego pueden abrir la interfaz AFK." + no-permission: "&cNo tienes permiso para abrir la interfaz AFK." + bypass: + no-permission: "&cNo tienes permiso para cambiar el estado de bypass AFK." + enabled: "&a%target% ahora omitirá la detección AFK." + enabled-target: "&aLa detección AFK ahora se omite para ti." + disabled: "&a%target% ya no omitirá la detección AFK." + disabled-target: "&cLa detección AFK ya no se omite para ti." + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cNo se pudo encontrar a ese jugador." +economy: + enter: + charged: "&eHas pagado %amount% para ponerte AFK." + insufficient: "&cNecesitas al menos %amount% para ponerte AFK." + failed: "&cNo hemos podido cobrar tu cuenta para ponerte AFK." + skipped: "&eSe omitió la cuota de entrada AFK porque no se pudo cobrar tu cuenta." + recurring: + charged: "&eHas pagado %amount% para seguir AFK." + revoked-insufficient: "&cNo tienes suficientes fondos para seguir AFK, por lo que tu estado AFK ha sido revocado." + revoked-failed: "&cNo hemos podido cobrar tu cuenta para seguir AFK, por lo que tu estado AFK ha sido revocado." + skipped: "&eSe omitió la cuota AFK recurrente porque no se pudo cobrar tu cuenta. Lo intentaremos de nuevo más tarde." + failed: "&cNo hemos podido cobrar tu cuenta para seguir AFK. Lo intentaremos de nuevo más tarde." +gui: + overview: + active: + no-permission: "&cNo tienes permiso para ver a los jugadores activos." + actions: + no-permission: "&cNo tienes permiso para usar las acciones de jugadores." + error: + player-not-found: "&cNo pudimos encontrar al jugador asociado a esa acción." + no-command: "&cNo se ha configurado ningún comando para esta acción." + default-kick-message: "&cHas sido expulsado por permanecer AFK demasiado tiempo." +unafk: + broadcast: "&aEl jugador&7 %player% &aya no está AFK" + title: + title: "&a¡Bienvenido de nuevo!" + subtitle: "&7Ya no estás AFK" +kick: + message: "&cHas sido expulsado de este servidor por permanecer AFK demasiado tiempo!" + +afkzone: + reward: + granted: + economy: "&aHas recibido %amount% por permanecer AFK en %zone%." + command: "&aHas recibido una recompensa por comando por permanecer AFK en %zone%." + item: "&aHas recibido %count%x %item% por permanecer AFK en %zone%." + default: "&aHas recibido una recompensa por permanecer AFK en %zone%." + add: + selection-missing: "&cNo se encontró una selección de WorldEdit: selecciona una región o proporciona coordenadas." + coords-required: "&cWorldEdit no está disponible. Proporciona coordenadas: /afk zone add " + coords-invalid: "&cCoordenadas inválidas. Usa números." + pos1: + set: "&aPosición 1 establecida. Usa /afk zone pos2 para establecer la segunda esquina." + pos2: + set: "&aPosición 2 establecida. Usa /afk zone add para crear la zona." + pos-cleared-self: "&aTus posiciones guardadas han sido borradas." + pos-cleared-target: "&aPosiciones borradas para %player%." + limit: + reached: "&cYa has alcanzado %reward_limit% para %reward_name%, vuelve en %time_remaining%." + cooldown: "&cYa has alcanzado %reward_limit% para %reward_name%, vuelve en %time_remaining%." + + reset: + success: "&aReiniciadas las cuentas de recompensa para %player%." + zone: + success: "&aReiniciadas las cuentas de recompensa para %player% en la zona %zone%." diff --git a/ezafk-core/src/main/resources/messages/messages_ja.yml b/ezafk-core/src/main/resources/messages/messages_ja.yml new file mode 100644 index 0000000..aef5b1b --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_ja.yml @@ -0,0 +1,109 @@ +anti: + vehicle: "&c乗り物に乗っている間はAFKを切り替えることはできません。" + infinite-waterflow: "&c水流トリックはAFK検出を回避できません。" + bubble-column: "&cバブルカラムを使ってAFK検出を回避することはできません。" +afk: + now: "&eあなたはAFKとしてマークされました。移動してアクティブを保持してください!" + broadcast: "&cプレイヤー&a %player% &cがAFKになりました" + no-longer: "&aおかえりなさい!あなたはもはやAFKではありません。" + title: + title: "&eAFK" + subtitle: "&7あなたは現在AFKです" +command: + usage: "&c使い方: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&c自分のAFK状態を管理できるのはゲーム内のプレイヤーのみです。" + no-permission: "&c他のプレイヤーのAFKを切り替える権限がありません。" + now-afk: "&a%target% はAFKとしてマークされました。" + no-longer-afk: "&a%target% はもはやAFKではありません。" + reload: + no-permission: "&c設定をリロードする権限がありません。" + success: "&a設定を正常にリロードしました。" + gui: + players-only: "&cAFK GUIを開けるのはゲーム内のプレイヤーのみです。" + no-permission: "&cAFK GUIを開く権限がありません。" + bypass: + no-permission: "&cAFKバイパスを変更する権限がありません。" + enabled: "&a%target% はこれからAFK検出をバイパスします。" + enabled-target: "&aあなたはこれからAFK検出をバイパスします。" + disabled: "&a%target% はこれからAFK検出をバイパスしません。" + disabled-target: "&cあなたはもはやAFK検出をバイパスしていません。" + info: + no-permission: "&cAFKレポートを見る権限がありません。" + not-afk: "&e%target% は現在AFKではありません。" + header: "&6%player% のAFKレポート &6:" + reason: "&7理由: &f%reason%" + detail: "&7詳細: &f%detail%" + no-detail: "&7詳細: &f追加の詳細は記録されていません。" + duration: "&7AFK時間: &f%duration%" + last-activity: "&7最終アクティビティ: &f%last% 前" + time: + players-only: "&c自分のAFK時間を確認できるのはプレイヤーのみです。" + no-permission: "&c自分のAFK時間を見る権限がありません。" + others-no-permission: "&c他のプレイヤーのAFK時間を見る権限がありません。" + self: "&aあなたの合計AFK時間: %duration%。" + other: "&a%player% の合計AFK時間: %duration%。" + top: + no-permission: "&cAFKランキングを見る権限がありません。" + empty: "&eまだ記録されたAFK時間はありません。" + header: "&6AFKランキング:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cそのプレイヤーは見つかりませんでした。" +economy: + enter: + charged: "&eAFKになるために %amount% を支払いました。" + insufficient: "&cAFKになるには少なくとも %amount% が必要です。" + failed: "&cアカウントからの課金に失敗したためAFKになれませんでした。" + skipped: "&eアカウントの課金ができなかったため、AFKエントリー料金はスキップされました。" + recurring: + charged: "&eAFK状態を維持するために %amount% を支払いました。" + revoked-insufficient: "&c残高不足のためAFKが解除されました。" + revoked-failed: "&c課金に失敗したためAFKが解除されました。" + skipped: "&e定期料金の課金ができなかったためスキップされました。後で再試行します。" + failed: "&c定期課金に失敗しました。後で再試行します。" +gui: + overview: + active: + no-permission: "&cアクティブなプレイヤーを表示する権限がありません。" + actions: + no-permission: "&cプレイヤーアクションを使用する権限がありません。" + error: + player-not-found: "&cそのアクションに紐づくプレイヤーが見つかりませんでした。" + no-command: "&cこのアクションに設定されたコマンドがありません。" + default-kick-message: "&c長時間AFKだったためキックされました。" +unafk: + broadcast: "&aプレイヤー&7 %player% &aはもはやAFKではありません" + title: + title: "&aおかえりなさい!" + subtitle: "&7あなたはもはやAFKではありません" +kick: + message: "&c長時間AFKだったためサーバーからキックされました!" + + +afkzone: + reward: + granted: + economy: "&a%zone% に滞在して %amount% を受け取りました。" + command: "&a%zone% に滞在してコマンド報酬を受け取りました。" + item: "&a%zone% に滞在して %count%x %item% を受け取りました。" + default: "&a%zone% に滞在して報酬を受け取りました。" + limit: + reached: "&c%reward_name% の上限 (%reward_limit%) に達しました。%time_remaining% 後に再試行してください。" + cooldown: "&c%reward_name% の上限 (%reward_limit%) に達しました。%time_remaining% 後に再試行してください。" + + add: + selection-missing: "&cWorldEditの選択が見つかりません - 領域を選択するか座標を指定してください。" + coords-required: "&cWorldEditが利用できません。座標を指定してください: /afk zone add " + coords-invalid: "&c無効な座標です。数値を使用してください。" + pos1: + set: "&a位置1を設定しました。位置2は /afk zone pos2 で設定してください。" + pos2: + set: "&a位置2を設定しました。/afk zone add でゾーンを作成してください。" + pos-cleared-self: "&a保存された位置をクリアしました。" + pos-cleared-target: "&a%player% の保存された位置をクリアしました。" + + reset: + success: "&a%player% の報酬回数をリセットしました。" + zone: + success: "&a%player% の %zone% における報酬回数をリセットしました。" diff --git a/ezafk-core/src/main/resources/messages/messages_nl.yml b/ezafk-core/src/main/resources/messages/messages_nl.yml new file mode 100644 index 0000000..1f8892a --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_nl.yml @@ -0,0 +1,99 @@ +anti: + vehicle: "&cJe kunt AFK niet toggelen terwijl je in een voertuig zit." + infinite-waterflow: "&cWaterstroomtrucs omzeilen de AFK-detectie niet." + bubble-column: "&cJe kunt geen bubbelkolommen gebruiken om AFK-detectie te omzeilen." +afk: + now: "&eJe staat nu als AFK gemarkeerd. Beweeg om actief te blijven!" + broadcast: "&cSpeler&a %player% &cis nu AFK" + no-longer: "&aWelkom terug! Je bent niet langer AFK." + title: + title: "&eAFK" + subtitle: "&7Je bent nu AFK" +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cAlleen spelers in het spel kunnen hun eigen AFK-status beheren." + no-permission: "&cJe hebt geen toestemming om de AFK-status van een andere speler te wijzigen." + now-afk: "&a%target% staat nu als AFK gemarkeerd." + no-longer-afk: "&a%target% staat niet langer als AFK gemarkeerd." + reload: + no-permission: "&cJe hebt geen toestemming om de configuratie te herladen." + success: "&aConfiguratie is succesvol herladen." + gui: + players-only: "&cAlleen spelers in het spel kunnen de AFK-GUI openen." + no-permission: "&cJe hebt geen toestemming om de AFK-GUI te openen." + bypass: + no-permission: "&cJe hebt geen toestemming om de AFK-bypassstatus te wijzigen." + enabled: "&a%target% zal AFK-detectie nu omzeilen." + + add: + selection-missing: "&cGeen WorldEdit-selectie gevonden - selecteer een regio of geef coördinaten op." + coords-required: "&cWorldEdit niet beschikbaar. Geef coördinaten op: /afk zone add " + coords-invalid: "&cOngeldige coördinaten opgegeven. Gebruik nummers." + pos1: + set: "&aPositie 1 ingesteld. Gebruik /afk zone pos2 om de tweede hoek in te stellen." + pos2: + set: "&aPositie 2 ingesteld. Gebruik /afk zone add om de zone te maken." + pos-cleared-self: "&aJe opgeslagen posities zijn gewist." + pos-cleared-target: "&aOpgeslagen posities gewist voor %player%." + enabled-target: "&aAFK-detectie wordt nu voor jou omzeild." + disabled: "&a%target% zal AFK-detectie niet langer omzeilen." + disabled-target: "&cAFK-detectie wordt niet langer voor jou omzeild." + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cDie speler kon niet worden gevonden." +economy: + enter: + charged: "&eJe hebt %amount% betaald om AFK te gaan." + insufficient: "&cJe hebt minstens %amount% nodig om AFK te gaan." + failed: "&cWe konden je account niet belasten om AFK te gaan." + skipped: "&eDe AFK-invoerprijs is overgeslagen omdat je account niet kon worden belast." + recurring: + charged: "&eJe hebt %amount% betaald om AFK te blijven." + revoked-insufficient: "&cJe hebt niet genoeg saldo om AFK te blijven, daarom is je AFK-status verwijderd." + revoked-failed: "&cWe konden je account niet belasten om AFK te blijven, daarom is je AFK-status verwijderd." + skipped: "&eDe terugkerende AFK-kost is overgeslagen omdat je account niet kon worden belast. We proberen het later opnieuw." + failed: "&cWe konden je account niet belasten om AFK te blijven. We proberen het later opnieuw." +gui: + overview: + active: + no-permission: "&cJe hebt geen toestemming om actieve spelers te bekijken." + actions: + no-permission: "&cJe hebt geen toestemming om speleracties te gebruiken." + error: + player-not-found: "&cWe konden de speler die aan die actie gekoppeld is niet vinden." + no-command: "&cEr is geen commando geconfigureerd voor deze actie." + default-kick-message: "&cJe bent gekickt omdat je te lang AFK was." +unafk: + broadcast: "&aSpeler&7 %player% &ais niet langer AFK" + title: + title: "&aWelkom terug!" + subtitle: "&7Je bent niet langer AFK" +kick: + message: "&cJe bent van deze server gekickt omdat je te lang AFK was!" + +afkzone: + reward: + granted: + economy: "&aJe ontving %amount% voor het blijven AFK in %zone%." + command: "&aJe ontving een commando-beloning voor het blijven AFK in %zone%." + item: "&aJe ontving %count%x %item% voor het blijven AFK in %zone%." + default: "&aJe ontving een beloning voor het blijven AFK in %zone%." + limit: + reached: "&cJe hebt al %reward_limit% bereikt voor %reward_name%, kom terug over %time_remaining%." + cooldown: "&cJe hebt al %reward_limit% bereikt voor %reward_name%, kom terug over %time_remaining%." + + reset: + success: "&aBeloningsaantallen gereset voor %player%." + zone: + success: "&aBeloningsaantallen gereset voor %player% in zone %zone%." diff --git a/ezafk-core/src/main/resources/messages/messages_ru.yml b/ezafk-core/src/main/resources/messages/messages_ru.yml new file mode 100644 index 0000000..fe907c3 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_ru.yml @@ -0,0 +1,100 @@ +anti: + vehicle: "&cВы не можете переключить режим AFK, находясь в транспорте." + infinite-waterflow: "&cХитрости с потоками воды не помогут обойти обнаружение AFK." + bubble-column: "&cВы не можете использовать пузырьковые столбы, чтобы обойти обнаружение AFK." +afk: + now: "&eТеперь вы отмечены как AFK. Двигайтесь, чтобы оставаться активным!" + broadcast: "&cИгрок&a %player% &cтеперь AFK" + no-longer: "&aС возвращением! Вы больше не AFK." + title: + title: "&eAFK" + subtitle: "&7Вы теперь AFK" + add: + selection-missing: "&cВы не выбрали регион WorldEdit - выберите регион или укажите координаты." + coords-required: "&cWorldEdit недоступен. Укажите координаты: /afk zone add " + coords-invalid: "&cНеверные координаты. Используйте числа." + pos1: + set: "&aПозиция 1 установлена. Используйте /afk zone pos2 для установки второй точки." + pos2: + set: "&aПозиция 2 установлена. Используйте /afk zone add чтобы создать зону." + pos-cleared-self: "&aВаши сохранённые позиции были очищены." + pos-cleared-target: "&aСохранённые позиции очищены для %player%." +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&cТолько игроки в игре могут управлять своим статусом AFK." + failed: "&cНе удалось обновить ваш статус AFK." + no-permission: "&cУ вас нет прав переключать статус AFK другого игрока." + now-afk: "&a%target% теперь помечен как AFK." + no-longer-afk: "&a%target% больше не помечен как AFK." + failed: "&cНе удалось изменить статус AFK игрока %target%." + reload: + no-permission: "&cУ вас нет прав перезагружать конфигурацию." + success: "&aКонфигурация успешно перезагружена." + gui: + players-only: "&cОткрыть AFK-интерфейс могут только игроки в игре." + no-permission: "&cУ вас нет прав открывать AFK-интерфейс." + bypass: + no-permission: "&cУ вас нет прав изменять обход обнаружения AFK." + enabled: "&a%target% теперь будет обходить обнаружение AFK." + enabled-target: "&aДля вас теперь включён обход обнаружения AFK." + disabled: "&a%target% больше не будет обходить обнаружение AFK." + disabled-target: "&cОбход обнаружения AFK для вас выключен." + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&cНе удалось найти этого игрока." +economy: + enter: + charged: "&eВы заплатили %amount%, чтобы перейти в режим AFK." + insufficient: "&cЧтобы перейти в режим AFK, нужно как минимум %amount%." + failed: "&cНе удалось списать средства за переход в режим AFK." + skipped: "&eПлата за переход в режим AFK пропущена, так как не удалось списать средства." + recurring: + charged: "&eВы заплатили %amount%, чтобы оставаться в режиме AFK." + revoked-insufficient: "&cНедостаточно средств, чтобы оставаться AFK, поэтому статус снят." + revoked-failed: "&cНе удалось списать средства, чтобы оставаться AFK, поэтому статус снят." + skipped: "&eПериодический платёж за режим AFK пропущен, так как не удалось списать средства. Мы попробуем позже." + failed: "&cНе удалось списать средства за пребывание в режиме AFK. Мы попробуем позже." +gui: + overview: + active: + no-permission: "&cУ вас нет прав просматривать активных игроков." + actions: + no-permission: "&cУ вас нет прав использовать действия с игроками." + error: + player-not-found: "&cНе удалось найти игрока, связанного с этим действием." + no-command: "&cДля этого действия не настроена команда." + default-kick-message: "&cВы были кикнуты за слишком долгое пребывание AFK." +unafk: + broadcast: "&aИгрок&7 %player% &aбольше не AFK" + title: + title: "&aС возвращением!" + subtitle: "&7Вы больше не AFK" +kick: + message: "&cВы были кикнуты с сервера за слишком долгое пребывание AFK!" + +afkzone: + reward: + granted: + economy: "&aВы получили %amount% за пребывание AFK в %zone%." + command: "&aВы получили награду в виде команды за пребывание AFK в %zone%." + item: "&aВы получили %count%x %item% за пребывание AFK в %zone%." + default: "&aВы получили награду за пребывание AFK в %zone%." + limit: + reached: "&cВы уже достигли %reward_limit% для %reward_name%, вернитесь через %time_remaining%." + cooldown: "&cВы уже достигли %reward_limit% для %reward_name%, вернитесь через %time_remaining%." + + reset: + success: "&aСброшены счётчики наград для %player%." + zone: + success: "&aСброшены счётчики наград для %player% в зоне %zone%." diff --git a/ezafk-core/src/main/resources/messages/messages_zh.yml b/ezafk-core/src/main/resources/messages/messages_zh.yml new file mode 100644 index 0000000..b16a818 --- /dev/null +++ b/ezafk-core/src/main/resources/messages/messages_zh.yml @@ -0,0 +1,101 @@ +anti: + vehicle: "&c骑乘交通工具时无法切换 AFK 状态。" + infinite-waterflow: "&c利用流水也无法绕过 AFK 检测。" + bubble-column: "&c你不能利用气泡柱来逃避挂机检测。" +afk: + now: "&e你现在被标记为 AFK。请活动一下保持在线!" + broadcast: "&c玩家&a %player% &c现在处于 AFK 状态" + no-longer: "&a欢迎回来!你已不再 AFK。" + title: + title: "&eAFK" + subtitle: "&7你现在处于 AFK 状态" +command: + usage: "&cUsage: /afk [reload|gui|toggle |bypass |info |time [player]|top]" + toggle: + self: + only-players: "&c只有在线玩家才能管理自己的 AFK 状态。" + failed: "&c无法更新你的 AFK 状态。" + no-permission: "&c你没有权限切换其他玩家的 AFK 状态。" + now-afk: "&a%target% 现在已被标记为 AFK。" + no-longer-afk: "&a%target% 不再被标记为 AFK。" + failed: "&c无法更改 %target% 的 AFK 状态。" + reload: + no-permission: "&c你没有权限重新加载配置。" + success: "&a配置重新加载成功。" + gui: + players-only: "&c只有在线玩家才能打开 AFK 界面。" + no-permission: "&c你没有权限打开 AFK 界面。" + bypass: + no-permission: "&c你没有权限更改 AFK 检测的绕过状态。" + enabled: "&a%target% 将会绕过 AFK 检测。" + enabled-target: "&a你现在可以绕过 AFK 检测。" + disabled: "&a%target% 将不再绕过 AFK 检测。" + disabled-target: "&c你不再绕过 AFK 检测。" + time: + players-only: "&cOnly players can view their own AFK time." + no-permission: "&cYou don't have permission to view your AFK time." + others-no-permission: "&cYou don't have permission to view other players' AFK time." + self: "&aYou have been AFK for %duration% in total." + other: "&a%player% has been AFK for %duration% in total." + top: + no-permission: "&cYou don't have permission to view the AFK leaderboard." + empty: "&eNo AFK time has been recorded yet." + header: "&6Top AFK players:" + entry: "&e#%position% &7%player% - &f%duration%" + player-not-found: "&c无法找到该玩家。" +economy: + enter: + charged: "&e你支付了 %amount% 以进入 AFK 状态。" + insufficient: "&c进入 AFK 状态至少需要 %amount%。" + failed: "&c无法扣款以让你进入 AFK 状态。" + skipped: "&e由于无法扣款,已跳过 AFK 状态的入场费用。" + recurring: + charged: "&e你支付了 %amount% 以保持 AFK 状态。" + revoked-insufficient: "&c你没有足够的资金继续 AFK,因此 AFK 状态已被移除。" + revoked-failed: "&c无法扣款以保持 AFK 状态,因此你的 AFK 状态已被移除。" + skipped: "&e由于无法扣款,已跳过 AFK 状态的周期费用。我们稍后会再尝试。" + failed: "&c无法扣款以保持 AFK 状态。我们稍后会再尝试。" +gui: + overview: + active: + no-permission: "&c你没有权限查看在线玩家。" + actions: + no-permission: "&c你没有权限使用玩家操作。" + error: + player-not-found: "&c无法找到与该动作关联的玩家。" + no-command: "&c尚未为此动作配置指令。" + default-kick-message: "&c由于长时间 AFK,你已被踢出。" +unafk: + broadcast: "&a玩家&7 %player% &a不再 AFK" + title: + title: "&a欢迎回来!" + subtitle: "&7你已不再 AFK" +kick: + message: "&c由于长时间 AFK,你已被服务器踢出!" + +afkzone: + reward: + granted: + economy: "&a你因在 %zone% 挂机而获得了 %amount%。" + command: "&a你因在 %zone% 挂机而获得了一次命令奖励。" + item: "&a你因在 %zone% 挂机而获得了 %count%x %item%。" + default: "&a你因在 %zone% 挂机而获得了一项奖励。" + limit: + reached: "&c你已达到 %reward_limit% 次 %reward_name%,请在 %time_remaining% 后再来。" + cooldown: "&c你已达到 %reward_limit% 次 %reward_name%,请在 %time_remaining% 后再来。" + + reset: + success: "&a已为 %player% 重置奖励计数。" + zone: + success: "&a已为 %player% 在区域 %zone% 重置奖励计数。" + + add: + selection-missing: "&c未找到 WorldEdit 选择 - 请选择一个区域或提供坐标。" + coords-required: "&c未检测到 WorldEdit。请提供坐标:/afk zone add " + coords-invalid: "&c提供的坐标无效。请使用数字。" + pos1: + set: "&a位置1 已设置。使用 /afk zone pos2 来设置第二个角。" + pos2: + set: "&a位置2 已设置。使用 /afk zone add 来创建区域。" + pos-cleared-self: "&a已清除你保存的位置。" + pos-cleared-target: "&a已为 %player% 清除保存的位置。" diff --git a/ezafk-core/src/main/resources/mp3/ezafk-sound.mp3 b/ezafk-core/src/main/resources/mp3/ezafk-sound.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..67652484c73fa7e5c7fcb365e73e73662a6bb22c GIT binary patch literal 720000 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 z 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�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

^b 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%0D09@UHeZ=}6fRFhHn?VSV&0Rn^` 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 EjHz?zvYwoM6TkIQ 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

  • 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 EOp8p~&5{lH=q3ndivK(P4I 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 |info |time [player]|top]" +permissions: + ezafk.reload: + description: "Allows reloading the EzAfk configuration" + default: op + ezafk.gui: + description: "Allows opening the AFK player overview GUI" + default: op + ezafk.gui.view-active: + description: "Allows viewing active (online) players in the AFK GUI" + default: op + ezafk.gui.actions: + description: "Allows using player actions in the AFK GUI" + default: op + ezafk.gui.view-active: + description: "Allows viewing active (online) players in the AFK GUI" + default: op + ezafk.gui.actions: + description: "Allows using player actions in the AFK GUI" + default: op + ezafk.toggle: + description: "Allows toggling other players' AFK state" + default: op + ezafk.bypass.manage: + description: "Allows toggling AFK bypass mode for players" + default: op + ezafk.info: + description: "Allows viewing AFK reports for players" + default: op + ezafk.time.reset: + description: "Allows resetting a player's total AFK time" + default: op + ezafk.time: + description: "Allows viewing your own total AFK time" + default: true + ezafk.zone.list: + description: "Allows listing AFK zones and viewing players in zones" + default: op + ezafk.zone.manage: + description: "Allows creating/removing/managing AFK zones and stored positions" + default: op + ezafk.time.others: + description: "Allows viewing other players' total AFK time" + default: op + ezafk.top: + description: "Allows viewing the AFK time leaderboard" + default: true + ezafk.economy.bypass: + description: "Bypass the economy cost for going AFK" + default: false +softdepend: +- "TAB" +- "Vault" +- "PlaceholderAPI" +- "EzEconomy" +- "voicechat" +- "WorldGuard" +- "WorldEdit" +- "EzCountdown" \ No newline at end of file diff --git a/ezafk-core/src/main/resources/zones.yml b/ezafk-core/src/main/resources/zones.yml new file mode 100644 index 0000000..b50d30b --- /dev/null +++ b/ezafk-core/src/main/resources/zones.yml @@ -0,0 +1,113 @@ +# AFK zones configuration +# Players inside these cuboids are exempt from AFK checks (or can earn rewards). +# Each region entry should include: world, x1, y1, z1, x2, y2, z2 +# Example: +# - world: world +# x1: 100 +# y1: 50 +# z1: 100 +# x2: 120 +# y2: 70 +# z2: 120 +enabled: false +regions: [] + +# ───────────────────────────────────────────────────────────────────────────── +# Global defaults for zone rewards and notifications. +# These values apply to every zone that does not define its own override. +# If a zone has its own "reward:" block, its values take precedence. +# ───────────────────────────────────────────────────────────────────────────── +defaults: + reward: + # Set to true to automatically enable economy rewards for all zones + # when a Vault-compatible economy plugin is present. + # Individual zones can override this with their own "reward.enabled" setting. + enabled: false + # Default interval in seconds between reward payouts. + interval-seconds: 300 + # Default currency amount deposited per payout (economy type only). + amount: 1.0 + # Default reward type: economy | command | item + type: economy + # ── EzCountdown notification defaults ─────────────────────────────────── + # When EzCountdown is installed, a live countdown is shown to the player + # ticking down to their next reward. Requires the EzCountdown integration + # to be enabled (integration.ezcountdown: auto|true in config.yml). + notification: + # Set to false to disable countdown notifications globally. + enabled: true + # EzCountdown display types. Valid values: + # ACTION_BAR, TITLE, BOSS_BAR, SCOREBOARD, DIALOG + # CHAT is always sent through EzAfk's own message system. + displays: [ACTION_BAR] + # Message shown in the countdown. Supports EzCountdown live placeholders: + # {seconds} – current remaining seconds (live) + # {formatted} – formatted duration (e.g. "1m 30s") + # and EzAfk placeholders: + # %zone% – zone name + # %amount% – reward amount per payout + message: "&7Next reward in &e{seconds}s &7in &a%zone%" + # How long the countdown runs in seconds. + # 0 means "use the zone's interval-seconds value" (the countdown ticks + # from the full interval down to 0, resetting on each reward). + duration: 0 + +# ───────────────────────────────────────────────────────────────────────────── +# Example region with economy rewards and EzCountdown notification: +# ───────────────────────────────────────────────────────────────────────────── +# - name: spawn +# world: world +# x1: 100 +# y1: 50 +# z1: 100 +# x2: 120 +# y2: 70 +# z2: 120 +# reward: +# enabled: true +# interval-seconds: 60 +# type: economy # economy|command|item +# amount: 5.0 +# # max-stack: 0 (0 or omitted = no stacking, positive = allow queued intervals) +# max-stack: 3 +# # Reward limit per player per cooldown window (0 = unlimited) +# limit: 10 +# limit-cooldown-seconds: 3600 +# # EzCountdown notification (overrides defaults.reward.notification) +# notification: +# enabled: true +# displays: [ACTION_BAR, BOSS_BAR] +# message: "&6Earn &e%amount% &6in &e{seconds}s &6at &a%zone%!" +# duration: 0 # 0 = use interval-seconds + +# Example command reward: +# - name: bonus +# world: world +# x1: 200 +# y1: 50 +# z1: 200 +# x2: 210 +# y2: 70 +# z2: 210 +# reward: +# enabled: true +# interval-seconds: 300 +# type: command +# command: "give %player% minecraft:gold_nugget 1" + +# Example item reward: +# - name: crate +# world: world +# x1: 300 +# y1: 50 +# z1: 300 +# x2: 305 +# y2: 55 +# z2: 305 +# reward: +# enabled: true +# interval-seconds: 120 +# type: item +# item: +# material: DIAMOND +# amount: 1 diff --git a/ezafk-folia/pom.xml b/ezafk-folia/pom.xml new file mode 100644 index 0000000..80694ab --- /dev/null +++ b/ezafk-folia/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + com.gyvex + ezafk-parent + 3.1.1 + + + ezafk-folia + jar + EzAfk Folia Adapter + + + + com.gyvex + ezafk-core + ${project.version} + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + true + + + + + \ No newline at end of file diff --git a/ezafk-folia/src/main/java/com/gyvex/ezafk/compatibility/scheduler/FoliaSchedulerAdapter.java b/ezafk-folia/src/main/java/com/gyvex/ezafk/compatibility/scheduler/FoliaSchedulerAdapter.java new file mode 100644 index 0000000..4736768 --- /dev/null +++ b/ezafk-folia/src/main/java/com/gyvex/ezafk/compatibility/scheduler/FoliaSchedulerAdapter.java @@ -0,0 +1,64 @@ +package com.gyvex.ezafk.compatibility.scheduler; + +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.TimeUnit; + +/** + * {@link SchedulerAdapter} backed by Folia's {@link GlobalRegionScheduler} and + * {@link AsyncScheduler}. + * + *

    This class directly imports Folia API types.

    + */ +public final class FoliaSchedulerAdapter implements SchedulerAdapter { + + private final Plugin plugin; + private final GlobalRegionScheduler global; + private final AsyncScheduler async; + + public FoliaSchedulerAdapter(Plugin plugin) { + this.plugin = plugin; + this.global = plugin.getServer().getGlobalRegionScheduler(); + this.async = plugin.getServer().getAsyncScheduler(); + } + + @Override + public TaskHandle runTaskTimer(Runnable task, long delayTicks, long periodTicks) { + ScheduledTask t = global.runAtFixedRate(plugin, st -> task.run(), + Math.max(1L, delayTicks), periodTicks); + return wrap(t); + } + + @Override + public TaskHandle runTaskTimerAsync(Runnable task, long delayTicks, long periodTicks) { + long delayMs = Math.max(50L, delayTicks * 50L); + long periodMs = Math.max(50L, periodTicks * 50L); + ScheduledTask t = async.runAtFixedRate(plugin, st -> task.run(), + delayMs, periodMs, TimeUnit.MILLISECONDS); + return wrap(t); + } + + @Override + public void runTaskAsync(Runnable task) { + async.runNow(plugin, st -> task.run()); + } + + @Override + public void runTaskLater(Runnable task, long delayTicks) { + if (delayTicks <= 0) { + global.run(plugin, st -> task.run()); + } else { + global.runDelayed(plugin, st -> task.run(), delayTicks); + } + } + + private static TaskHandle wrap(ScheduledTask task) { + return new TaskHandle() { + @Override public void cancel() { task.cancel(); } + @Override public boolean isCancelled() { return task.isCancelled(); } + }; + } +} \ No newline at end of file diff --git a/ezafk-paper-26.1/pom.xml b/ezafk-paper-26.1/pom.xml new file mode 100644 index 0000000..237913f --- /dev/null +++ b/ezafk-paper-26.1/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + com.gyvex + ezafk-parent + 3.1.1 + + + ezafk-paper-26.1 + jar + EzAfk Paper 26.1 Adapter + + + + com.gyvex + ezafk-api + ${project.version} + + + io.papermc.paper + paper-api + ${paper.version} + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + true + + + + + \ No newline at end of file diff --git a/ezafk-paper-26.1/src/main/java/com/gyvex/ezafk/version/Paper261PlusVersionAdapter.java b/ezafk-paper-26.1/src/main/java/com/gyvex/ezafk/version/Paper261PlusVersionAdapter.java new file mode 100644 index 0000000..347f2f5 --- /dev/null +++ b/ezafk-paper-26.1/src/main/java/com/gyvex/ezafk/version/Paper261PlusVersionAdapter.java @@ -0,0 +1,28 @@ +package com.gyvex.ezafk.version; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.potion.PotionEffectType; + +public final class Paper261PlusVersionAdapter implements VersionAdapter { + + @Override + public Attribute getMaxHealthAttribute() { + return Attribute.MAX_HEALTH; + } + + @Override + public PotionEffectType getEffect(String minecraftKey) { + RegistryAccess registryAccess = RegistryAccess.registryAccess(); + return registryAccess.getRegistry(RegistryKey.MOB_EFFECT).get(NamespacedKey.minecraft(minecraftKey)); + } + + @Override + public Enchantment getEnchantment(String minecraftKey) { + RegistryAccess registryAccess = RegistryAccess.registryAccess(); + return registryAccess.getRegistry(RegistryKey.ENCHANTMENT).get(NamespacedKey.minecraft(minecraftKey)); + } +} \ No newline at end of file diff --git a/ezafk/pom.xml b/ezafk/pom.xml new file mode 100644 index 0000000..0a7ce15 --- /dev/null +++ b/ezafk/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + + + com.gyvex + ezafk-parent + 3.1.1 + + + ezafk + jar + EzAfk Distribution + + + + com.gyvex + ezafk-api + ${project.version} + + + com.gyvex + ezafk-core + ${project.version} + + + com.gyvex + ezafk-paper-26.1 + ${project.version} + + + com.gyvex + ezafk-folia + ${project.version} + + + + + + + ${project.basedir}/../ezafk-core/src/main/resources + true + + plugin.yml + config.yml + gui.yml + mysql.yml + zones.yml + messages/** + + + + ${project.basedir}/../ezafk-core/src/main/resources + false + + mp3/ezafk-sound.mp3 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + true + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven.shade.plugin.version} + + + package + + shade + + + false + + + org.bstats:bstats-bukkit + org.bstats:bstats-base + net.kyori:adventure-api + net.kyori:adventure-key + net.kyori:adventure-text-minimessage + net.kyori:adventure-text-serializer-legacy + net.kyori:examination-api + net.kyori:examination-string + javazoom:jlayer + com.github.EzFramework:Jaloquent + com.github.EzFramework:JavaQueryBuilder + + com.gyvex:ezafk-api + com.gyvex:ezafk-core + com.gyvex:ezafk-paper-26.1 + com.gyvex:ezafk-folia + + + + + org.bstats + com.gyvex.ezafk.bstats + + + net.kyori + com.gyvex.ezafk.libs.net.kyori + + + javazoom + com.gyvex.ezafk.libs.javazoom + + + com.github.ezframework + com.gyvex.ezafk.libs.ezframework + + + + + + + + + \ No newline at end of file From 9b6c2051db0332f6cd56195a2c86b21fc34d343e Mon Sep 17 00:00:00 2001 From: ez-plugins Date: Wed, 15 Jul 2026 16:51:58 +0200 Subject: [PATCH 05/11] fix: cleanup old duplicates; refactored workflows to multi module setup --- .github/workflows/ci.yml | 15 +- .github/workflows/release.yml | 8 +- ezafk/pom.xml | 8 + pom.xml | 412 +------------ src/main/java/com/gyvex/ezafk/EzAfk.java | 37 -- .../com/gyvex/ezafk/bootstrap/Bootstrap.java | 256 -------- .../com/gyvex/ezafk/bootstrap/Registry.java | 187 ------ .../gyvex/ezafk/command/AfkZoneCommand.java | 299 ---------- .../com/gyvex/ezafk/command/EzAfkCommand.java | 484 --------------- .../ezafk/command/EzAfkTabCompleter.java | 132 ----- .../gyvex/ezafk/compatibility/LoreUtil.java | 115 ---- .../ezafk/compatibility/ParticleCompat.java | 49 -- .../inventory/InventoryCompat.java | 40 -- .../ezafk/compatibility/item/HeadCompat.java | 89 --- .../compatibility/item/ItemMetaCompat.java | 139 ----- .../compatibility/item/MaterialCompat.java | 45 -- .../platform/PlatformDetector.java | 28 - .../player/PlayerDisplayCompat.java | 95 --- .../player/PlayerKickCompat.java | 42 -- .../scheduler/BukkitSchedulerAdapter.java | 50 -- .../scheduler/FoliaSchedulerAdapter.java | 69 --- .../scheduler/SchedulerAdapter.java | 21 - .../scheduler/SchedulerAdapterFactory.java | 34 -- .../compatibility/scheduler/TaskHandle.java | 7 - .../compatibility/sound/SoundCompat.java | 48 -- .../com/gyvex/ezafk/config/ConfigManager.java | 264 --------- .../event/PlayerAfkStatusChangeEvent.java | 69 --- .../java/com/gyvex/ezafk/gui/GuiAction.java | 96 --- .../com/gyvex/ezafk/gui/GuiActionFactory.java | 31 - .../ezafk/integration/EconomyIntegration.java | 79 --- .../gyvex/ezafk/integration/Integration.java | 7 - .../ezafk/integration/MetricsIntegration.java | 42 -- .../PlaceholderApiIntegration.java | 43 -- .../integration/PlayerListNameAdapter.java | 16 - .../SimpleVoiceChatIntegration.java | 5 - .../ezafk/integration/SpigotIntegration.java | 36 -- .../ezafk/integration/TabIntegration.java | 561 ------------------ .../integration/VoiceChatIntegration.java | 25 - .../integration/WorldEditIntegration.java | 136 ----- .../integration/WorldGuardIntegration.java | 118 ---- .../ezcountdown/EzCountdownIntegration.java | 300 ---------- .../EzAfkPlaceholderExpansion.java | 231 -------- .../integration/spigot/UpdateChecker.java | 32 - .../tab/PlayerListNameAdapter.java | 16 - .../tab/TabApiPlayerListNameAdapter.java | 345 ----------- .../voicechat/EzAfkVoicechatPlugin.java | 28 - .../EzAfkVoicechatServiceRegistrar.java | 13 - .../voicechat/SimpleVoiceChatIntegration.java | 169 ------ .../worldedit/LegacyWorldEditHelper.java | 88 --- .../worldedit/WorldEditHelper.java | 138 ----- .../integration/worldguard/FlagRegistrar.java | 87 --- .../worldguard/WorldGuardSupport.java | 57 -- .../worldguard/flag/AfkBypassFlag.java | 52 -- .../ezafk/listener/AfkPlayerActionsGUI.java | 275 --------- .../ezafk/listener/AfkPlayerOverviewGUI.java | 354 ----------- .../listener/EconomyServiceListener.java | 83 --- .../gyvex/ezafk/listener/MoveListener.java | 246 -------- .../listener/PlayerActivityListener.java | 48 -- .../ezafk/listener/PlayerQuitListener.java | 43 -- .../listener/SimpleVoiceChatAfkListener.java | 26 - .../gyvex/ezafk/manager/AfkTimeManager.java | 437 -------------- .../gyvex/ezafk/manager/AfkZoneManager.java | 221 ------- .../ezafk/manager/AfkZoneRewardManager.java | 363 ------------ .../com/gyvex/ezafk/manager/BypassList.java | 77 --- .../ezafk/manager/BypassListManager.java | 119 ---- .../gyvex/ezafk/manager/EconomyManager.java | 397 ------------- .../ezafk/manager/IntegrationManager.java | 59 -- .../gyvex/ezafk/manager/MessageManager.java | 91 --- .../com/gyvex/ezafk/manager/MySQLManager.java | 418 ------------- .../ezafk/manager/ZoneRewardStatsManager.java | 126 ---- .../gyvex/ezafk/repository/AfkTimeModel.java | 34 -- .../ezafk/repository/StorageFactory.java | 36 -- .../ezafk/repository/StorageRepository.java | 17 - .../ezafk/repository/mysql/MySQLStorage.java | 155 ----- .../repository/sqlite/SQLiteStorage.java | 138 ----- .../ezafk/repository/yaml/YamlDataStore.java | 152 ----- .../ezafk/repository/yaml/YamlStorage.java | 95 --- .../gyvex/ezafk/state/AfkActivationMode.java | 18 - .../java/com/gyvex/ezafk/state/AfkReason.java | 24 - .../java/com/gyvex/ezafk/state/AfkState.java | 414 ------------- .../gyvex/ezafk/state/AfkStatusDetails.java | 34 -- .../gyvex/ezafk/state/LastActiveState.java | 34 -- .../com/gyvex/ezafk/state/StateAnimator.java | 48 -- .../com/gyvex/ezafk/state/ToggleResult.java | 13 - .../com/gyvex/ezafk/task/AfkCheckTask.java | 331 ----------- .../com/gyvex/ezafk/task/TaskManager.java | 20 - .../com/gyvex/ezafk/util/CommandUtil.java | 28 - .../gyvex/ezafk/util/DurationFormatter.java | 42 -- .../com/gyvex/ezafk/util/PlaceholderUtil.java | 154 ----- src/main/java/com/gyvex/ezafk/zone/Zone.java | 82 --- .../java/com/gyvex/ezafk/zone/ZoneCache.java | 44 -- src/main/resources/config.yml | 210 ------- src/main/resources/gui.yml | 49 -- src/main/resources/messages/messages.yml | 142 ----- src/main/resources/messages/messages_de.yml | 108 ---- src/main/resources/messages/messages_en.yml | 109 ---- src/main/resources/messages/messages_es.yml | 98 --- src/main/resources/messages/messages_ja.yml | 109 ---- src/main/resources/messages/messages_nl.yml | 99 ---- src/main/resources/messages/messages_ru.yml | 100 ---- src/main/resources/messages/messages_zh.yml | 101 ---- src/main/resources/mp3/ezafk-sound.mp3 | Bin 720000 -> 0 bytes src/main/resources/mysql.yml | 7 - src/main/resources/plugin.yml | 69 --- src/main/resources/zones.yml | 113 ---- src/test/java/com/gyvex/ezafk/SmokeTest.java | 13 - .../java/com/gyvex/ezafk/TestHelpers.java | 40 -- .../gyvex/ezafk/WorldGuardAbsenceTest.java | 37 -- .../compatibility/LoreUtilSmokeTest.java | 164 ----- .../MiniMessagePlaceholderFeatureTest.java | 96 --- .../ezafk/feature/AfkStateFeatureTest.java | 154 ----- .../feature/AfkZoneCommandFeatureTest.java | 49 -- .../ezafk/feature/AfkZoneMoreFeatureTest.java | 45 -- ...rapIntegrationRegistrationFeatureTest.java | 76 --- .../feature/BypassListCommandFeatureTest.java | 264 --------- .../feature/CommandCoverageFeatureTest.java | 183 ------ .../CommandPermissionCoverageFeatureTest.java | 79 --- .../feature/EzAfkCommandFeatureTest.java | 77 --- .../feature/EzAfkMoreCommandsFeatureTest.java | 60 -- .../ezafk/feature/EzAfkToggleFeatureTest.java | 34 -- .../ezafk/feature/GUIOpenFeatureTest.java | 45 -- .../GuiAndListenerCoverageFeatureTest.java | 400 ------------- .../GuiInternalCoverageFeatureTest.java | 94 --- .../MoveListenerAntiBypassFeatureTest.java | 100 ---- .../feature/MoveListenerFeatureTest.java | 55 -- .../MoveListenerInternalCoverageTest.java | 240 -------- .../PlayerActivityListenerAllEventsTest.java | 76 --- .../PlayerActivityListenerFeatureTest.java | 41 -- ...PlayerAfkStatusChangeEventFeatureTest.java | 163 ----- .../ezafk/feature/PlayerQuitAfkFlushTest.java | 75 --- .../PlayerQuitListenerFeatureTest.java | 49 -- .../feature/TabCompletionFeatureTest.java | 52 -- .../integration/EconomyIntegrationTest.java | 84 --- .../integration/IntegrationTestFixtures.java | 38 -- .../integration/MetricsIntegrationTest.java | 44 -- .../PlaceholderApiIntegrationTest.java | 48 -- .../integration/WorldEditIntegrationTest.java | 55 -- .../placeholder/PlaceholderExpansionTest.java | 152 ----- .../worldguard/FlagRegistrarTest.java | 81 --- .../integration/worldguard/TestRegistry.java | 24 - .../ezafk/manager/AfkTimeManagerTest.java | 126 ---- .../ezafk/manager/BypassListManagerTest.java | 242 -------- .../ezafk/manager/EconomyManagerTest.java | 78 --- .../ezafk/manager/IntegrationManagerTest.java | 105 ---- .../repository/AfkTimeModelCoreTest.java | 22 - .../ezafk/repository/AfkTimeModelFactory.java | 14 - .../StorageFactoryAndSqlBackendsCoreTest.java | 113 ---- .../mysql/MySQLStorageTestcontainersTest.java | 74 --- .../yaml/YamlDataStoreCoreTest.java | 103 ---- .../repository/yaml/YamlStorageCoreTest.java | 83 --- .../state/AfkStatusDetailsSmokeTest.java | 57 -- .../ezafk/state/LastActiveStateTest.java | 92 --- .../com/gyvex/ezafk/zone/ZoneCacheTest.java | 114 ---- .../gyvex/ezafk/zone/ZoneContainsTest.java | 94 --- .../protection/flags/StateFlag.java | 23 - 155 files changed, 32 insertions(+), 16998 deletions(-) delete mode 100644 src/main/java/com/gyvex/ezafk/EzAfk.java delete mode 100644 src/main/java/com/gyvex/ezafk/bootstrap/Bootstrap.java delete mode 100644 src/main/java/com/gyvex/ezafk/bootstrap/Registry.java delete mode 100644 src/main/java/com/gyvex/ezafk/command/AfkZoneCommand.java delete mode 100644 src/main/java/com/gyvex/ezafk/command/EzAfkCommand.java delete mode 100644 src/main/java/com/gyvex/ezafk/command/EzAfkTabCompleter.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/LoreUtil.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/ParticleCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/inventory/InventoryCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/item/HeadCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/item/ItemMetaCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/item/MaterialCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/platform/PlatformDetector.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/player/PlayerDisplayCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/player/PlayerKickCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/scheduler/BukkitSchedulerAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/scheduler/FoliaSchedulerAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/scheduler/SchedulerAdapterFactory.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/scheduler/TaskHandle.java delete mode 100644 src/main/java/com/gyvex/ezafk/compatibility/sound/SoundCompat.java delete mode 100644 src/main/java/com/gyvex/ezafk/config/ConfigManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/event/PlayerAfkStatusChangeEvent.java delete mode 100644 src/main/java/com/gyvex/ezafk/gui/GuiAction.java delete mode 100644 src/main/java/com/gyvex/ezafk/gui/GuiActionFactory.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/EconomyIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/Integration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/MetricsIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/PlaceholderApiIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/PlayerListNameAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/SimpleVoiceChatIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/SpigotIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/TabIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/VoiceChatIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/WorldEditIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/WorldGuardIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/ezcountdown/EzCountdownIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/placeholder/EzAfkPlaceholderExpansion.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/spigot/UpdateChecker.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/tab/PlayerListNameAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/tab/TabApiPlayerListNameAdapter.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatPlugin.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/voicechat/EzAfkVoicechatServiceRegistrar.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/voicechat/SimpleVoiceChatIntegration.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/worldedit/LegacyWorldEditHelper.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/worldedit/WorldEditHelper.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrar.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/worldguard/WorldGuardSupport.java delete mode 100644 src/main/java/com/gyvex/ezafk/integration/worldguard/flag/AfkBypassFlag.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/AfkPlayerActionsGUI.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/AfkPlayerOverviewGUI.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/EconomyServiceListener.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/MoveListener.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/PlayerActivityListener.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/PlayerQuitListener.java delete mode 100644 src/main/java/com/gyvex/ezafk/listener/SimpleVoiceChatAfkListener.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/AfkTimeManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/AfkZoneManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/AfkZoneRewardManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/BypassList.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/BypassListManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/EconomyManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/IntegrationManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/MessageManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/MySQLManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/manager/ZoneRewardStatsManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/AfkTimeModel.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/StorageFactory.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/StorageRepository.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/mysql/MySQLStorage.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/sqlite/SQLiteStorage.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/yaml/YamlDataStore.java delete mode 100644 src/main/java/com/gyvex/ezafk/repository/yaml/YamlStorage.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/AfkActivationMode.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/AfkReason.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/AfkState.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/AfkStatusDetails.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/LastActiveState.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/StateAnimator.java delete mode 100644 src/main/java/com/gyvex/ezafk/state/ToggleResult.java delete mode 100644 src/main/java/com/gyvex/ezafk/task/AfkCheckTask.java delete mode 100644 src/main/java/com/gyvex/ezafk/task/TaskManager.java delete mode 100644 src/main/java/com/gyvex/ezafk/util/CommandUtil.java delete mode 100644 src/main/java/com/gyvex/ezafk/util/DurationFormatter.java delete mode 100644 src/main/java/com/gyvex/ezafk/util/PlaceholderUtil.java delete mode 100644 src/main/java/com/gyvex/ezafk/zone/Zone.java delete mode 100644 src/main/java/com/gyvex/ezafk/zone/ZoneCache.java delete mode 100644 src/main/resources/config.yml delete mode 100644 src/main/resources/gui.yml delete mode 100644 src/main/resources/messages/messages.yml delete mode 100644 src/main/resources/messages/messages_de.yml delete mode 100644 src/main/resources/messages/messages_en.yml delete mode 100644 src/main/resources/messages/messages_es.yml delete mode 100644 src/main/resources/messages/messages_ja.yml delete mode 100644 src/main/resources/messages/messages_nl.yml delete mode 100644 src/main/resources/messages/messages_ru.yml delete mode 100644 src/main/resources/messages/messages_zh.yml delete mode 100644 src/main/resources/mp3/ezafk-sound.mp3 delete mode 100644 src/main/resources/mysql.yml delete mode 100644 src/main/resources/plugin.yml delete mode 100644 src/main/resources/zones.yml delete mode 100644 src/test/java/com/gyvex/ezafk/SmokeTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/TestHelpers.java delete mode 100644 src/test/java/com/gyvex/ezafk/WorldGuardAbsenceTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/compatibility/LoreUtilSmokeTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/compatibility/MiniMessagePlaceholderFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/AfkStateFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/AfkZoneCommandFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/AfkZoneMoreFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/BootstrapIntegrationRegistrationFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/BypassListCommandFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/CommandCoverageFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/CommandPermissionCoverageFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/EzAfkCommandFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/EzAfkMoreCommandsFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/EzAfkToggleFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/GUIOpenFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/GuiAndListenerCoverageFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/GuiInternalCoverageFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/MoveListenerAntiBypassFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/MoveListenerFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/MoveListenerInternalCoverageTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/PlayerActivityListenerAllEventsTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/PlayerActivityListenerFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/PlayerAfkStatusChangeEventFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/PlayerQuitAfkFlushTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/PlayerQuitListenerFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/feature/TabCompletionFeatureTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/EconomyIntegrationTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/IntegrationTestFixtures.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/MetricsIntegrationTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/PlaceholderApiIntegrationTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/WorldEditIntegrationTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/placeholder/PlaceholderExpansionTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/worldguard/FlagRegistrarTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/integration/worldguard/TestRegistry.java delete mode 100644 src/test/java/com/gyvex/ezafk/manager/AfkTimeManagerTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/manager/BypassListManagerTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/manager/EconomyManagerTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/manager/IntegrationManagerTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/AfkTimeModelCoreTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/AfkTimeModelFactory.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/StorageFactoryAndSqlBackendsCoreTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/mysql/MySQLStorageTestcontainersTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/yaml/YamlDataStoreCoreTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/repository/yaml/YamlStorageCoreTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/state/AfkStatusDetailsSmokeTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/state/LastActiveStateTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/zone/ZoneCacheTest.java delete mode 100644 src/test/java/com/gyvex/ezafk/zone/ZoneContainsTest.java delete mode 100644 src/test/java/com/sk89q/worldguard/protection/flags/StateFlag.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a42299c..b065b81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: cache: maven - name: Build plugin JAR (JDK 25) - run: mvn -B -ntp -Dmaven.test.skip=true package + run: mvn -B -ntp -f pom.xml -Dmaven.test.skip=true package - name: Set up server JDK 21 uses: actions/setup-java@v5 @@ -137,7 +137,7 @@ jobs: - name: Configure server run: | mkdir -p smoke-server/plugins - cp target/ezafk-*.jar smoke-server/plugins/EzAfk.jar + cp ezafk/target/ezafk-*.jar smoke-server/plugins/EzAfk.jar cp spigot-1.21.4.jar smoke-server/ echo "eula=true" > smoke-server/eula.txt printf '%s\n' \ @@ -214,7 +214,7 @@ jobs: cache: maven - name: Build plugin JAR - run: mvn -B -ntp -Dmaven.test.skip=true package + run: mvn -B -ntp -f pom.xml -Dmaven.test.skip=true package - name: Resolve latest Paper 1.21.11 build id: paper @@ -237,7 +237,7 @@ jobs: -o smoke-server/paper.jar - name: Copy plugin JAR - run: cp target/ezafk-*.jar smoke-server/plugins/EzAfk.jar + run: cp ezafk/target/ezafk-*.jar smoke-server/plugins/EzAfk.jar - name: Configure server run: | @@ -316,7 +316,7 @@ jobs: cache: maven - name: Build plugin JAR - run: mvn -B -ntp -Dmaven.test.skip=true package + run: mvn -B -ntp -f pom.xml -Dmaven.test.skip=true package - name: Resolve latest Folia 1.21.11 build id: folia @@ -339,7 +339,7 @@ jobs: -o smoke-server/folia.jar - name: Copy plugin JAR - run: cp target/ezafk-*.jar smoke-server/plugins/EzAfk.jar + run: cp ezafk/target/ezafk-*.jar smoke-server/plugins/EzAfk.jar - name: Configure server run: | @@ -399,5 +399,4 @@ jobs: with: name: folia-1.21.11-java25-smoke-log path: smoke-server/server.log - retention-days: 7 - + retention-days: 7 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 465196b..ee22121 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,7 +80,7 @@ jobs: -DgenerateBackupPoms=false - name: Build - run: mvn -B -ntp -DskipTests package + run: mvn -B -ntp -f pom.xml -DskipTests package # ────────────────────────────────────────────────────────────────────── # 5. EXTRACT CHANGELOG @@ -119,7 +119,7 @@ jobs: gh release create "${{ steps.meta.outputs.ref }}" \ --title "EzAfk ${{ steps.meta.outputs.version }}" \ --notes-file release-notes.md \ - "target/ezafk-${{ steps.meta.outputs.version }}.jar" + "ezafk/target/ezafk-${{ steps.meta.outputs.version }}.jar" # ────────────────────────────────────────────────────────────────────── # 7. MODRINTH RELEASE @@ -133,7 +133,7 @@ jobs: name: "EzAfk ${{ steps.meta.outputs.version }}" version: "${{ steps.meta.outputs.version }}" version-type: release - files: target/ezafk-${{ steps.meta.outputs.version }}.jar + files: ezafk/target/ezafk-${{ steps.meta.outputs.version }}.jar game-versions: ">=1.19" game-versions-filter: release loaders: | @@ -185,4 +185,4 @@ jobs: curl --fail -s -X POST "$DISCORD_WEBHOOK" \ -H "Content-Type: application/json" \ - -d "$PAYLOAD" + -d "$PAYLOAD" \ No newline at end of file diff --git a/ezafk/pom.xml b/ezafk/pom.xml index 0a7ce15..e7fa59c 100644 --- a/ezafk/pom.xml +++ b/ezafk/pom.xml @@ -14,6 +14,14 @@ jar EzAfk Distribution + + + github + GitHub Packages + https://maven.pkg.github.com/${github.repository} + + + com.gyvex diff --git a/pom.xml b/pom.xml index 95e68b5..a7bdbd8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,17 +1,19 @@ - 4.0.0 com.gyvex - ezafk + ezafk-parent 3.1.1 + pom UTF-8 provided 21 + 3.5.5 26.1.2.build.57-stable 3.1.0 7.0.13 @@ -28,6 +30,14 @@ true + + ezafk-api + ezafk-core + ezafk-paper-26.1 + ezafk-folia + ezafk + + papermc-repo @@ -59,403 +69,7 @@ maxhenkel-public https://maven.maxhenkel.de/repository/public - - - - - io.papermc.paper - paper-api - ${paper.version} - provided - - - com.google.code.gson - gson - - - org.apache.commons - commons-lang3 - - - org.slf4j - slf4j-api - - - org.jetbrains - annotations - - - - - - com.google.code.gson - gson - 2.11.0 - provided - - - com.google.errorprone - error_prone_annotations - - - - - com.google.errorprone - error_prone_annotations - ${errorprone.annotations.version} - provided - - - org.jetbrains - annotations - ${jetbrains.annotations.version} - provided - - - org.apache.logging.log4j - log4j-api - ${log4j.api.version} - provided - - - org.slf4j - slf4j-api - 2.0.17 - provided - - - org.bstats - bstats-bukkit - ${bstats.version} - compile - - - com.sk89q.worldguard - worldguard-bukkit - ${worldguard.version} - provided - - - it.unimi.dsi - fastutil - - - org.yaml - snakeyaml - - - com.google.guava - guava - - - com.google.code.gson - gson - - - org.bstats - bstats-bukkit - - - org.apache.logging.log4j - log4j-api - - - - - - net.kyori - adventure-api - 5.2.0 - - - net.kyori - adventure-key - 5.2.0 - - - net.kyori - adventure-text-minimessage - 5.2.0 - - - net.kyori - adventure-text-serializer-legacy - 5.2.0 - - - net.kyori - examination-api - 1.3.0 - - - org.jetbrains - annotations - - - - - net.kyori - examination-string - 1.3.0 - - - org.jetbrains - annotations - - - - - javazoom - jlayer - 1.0.1 - - - - de.maxhenkel.voicechat - voicechat-api - 2.2.5 - provided - - - - com.github.ez-plugins - ezcountdown - 2.0.1 - provided - - - io.papermc.paper - paper-api - - - net.kyori - adventure-api - - - net.kyori - adventure-key - - - net.kyori - adventure-text-minimessage - - - net.kyori - adventure-text-serializer-legacy - - - org.bstats - bstats-bukkit - - - org.bstats - bstats-base - - - - - com.github.NEZNAMY - TAB-API - 5.5.0 - provided - - - org.jetbrains - annotations - - - - - com.github.MilkBowl - VaultAPI - 1.7.1 - provided - - - me.clip - placeholderapi - 2.12.2 - provided - - - org.jetbrains - annotations - - - org.bstats - bstats-bukkit - - - org.bstats - bstats-base - - - net.kyori - adventure-api - - - net.kyori - adventure-text-serializer-legacy - - - net.kyori - adventure-text-serializer-gson - - - - - com.github.EzFramework - Jaloquent - ${jaloquent.version} - compile - - - com.github.EzFramework - jaker - 1.0.6 - test - - - org.yaml - snakeyaml - - - org.apache.commons - commons-lang3 - - - - - com.github.EzFramework.Jaker - jaker-data-en-US - 1.0.6 - test - - - org.junit.jupiter - junit-jupiter - 5.10.2 - test - - - org.mockito - mockito-core - 5.5.0 - test - - - - - - - src/main/resources - true - - plugin.yml - config.yml - gui.yml - mysql.yml - zones.yml - messages/** - - - - src/main/resources - false - - mp3/ezafk-sound.mp3 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven.compiler.plugin.version} - - ${maven.compiler.release} - true - - - - org.apache.maven.plugins - maven-shade-plugin - ${maven.shade.plugin.version} - - - package - - shade - - - false - - - org.bstats:bstats-bukkit - org.bstats:bstats-base - net.kyori:adventure-api - net.kyori:adventure-key - net.kyori:adventure-text-minimessage - net.kyori:adventure-text-serializer-legacy - net.kyori:examination-api - net.kyori:examination-string - javazoom:jlayer - com.github.EzFramework:Jaloquent - com.github.EzFramework:JavaQueryBuilder - - - - - org.bstats - com.gyvex.ezafk.bstats - - - net.kyori - com.gyvex.ezafk.libs.net.kyori - - - javazoom - com.gyvex.ezafk.libs.javazoom - - - com.github.ezframework - com.gyvex.ezafk.libs.ezframework - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.5 - - ${maven.test.skip} - false - -Dnet.bytebuddy.experimental=true - - - - - - - - github - GitHub Packages - https://maven.pkg.github.com/${github.repository} - - +