diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80e2a94..7953419 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -158,6 +158,8 @@ jobs: teams-api(optional) ezcountdown(optional) ezeconomy(optional) + pvpmanager(optional) + simple-combatlog(optional) # ────────────────────────────────────────────────────────────────────── # 8. DISCORD NOTIFICATION diff --git a/CHANGELOG.md b/CHANGELOG.md index be5ecc2..ddd6dad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,29 @@ Release tags use the `v` prefix (e.g. `v3.0.2`). --- +## [3.3.0] - 2026-05-16 + +### Added + +- **PvP tag integration**: EzRTP now detects when a player enters combat and cancels their + pending teleport or active countdown. Three plugins are supported out of the box: + - **[CombatLogX](https://www.spigotmc.org/resources/combatlogx.31689/)** — auto-detected at startup; soft dependency. + - **[PvPManager](https://modrinth.com/plugin/pvpmanager)** — auto-detected at startup; soft dependency. + - **[Simple Combat Log](https://modrinth.com/plugin/simple-combatlog)** (NikeyV1/CombatLog) — auto-detected at startup; soft dependency. + - All three are added to `softdepend` in `plugin.yml` so they load before EzRTP when + present. No configuration change is required on servers that do not use any of them. + - New `pvp-tag-integration` section in `rtp.yml`: + - `cancel-countdown-on-pvp-tag` (default `true`): cancel an active countdown the moment + the player is tagged. + - `cancel-queued-on-pvp-tag` (default `true`): skip a queued teleport if the player is + already in combat when their slot is dispatched. + - New message keys `countdown-pvp-cancel` and `queue-pvp-tag-cancel` in `messages/*.yml` + for the cancellation notifications. + - Modrinth release metadata now declares `pvpmanager` and `simple-combatlog` as optional + dependencies. + +--- + ## [3.2.3] - 2026-05-16 ### Fixed diff --git a/docs/config/rtp.md b/docs/config/rtp.md index 4d55a34..c5e187f 100644 --- a/docs/config/rtp.md +++ b/docs/config/rtp.md @@ -230,6 +230,30 @@ Supported values: `worldguard`, `griefprevention`, `teamsapi`. --- +## PvP tag integration + +Cancels a pending teleport or active countdown when the player enters combat. +Requires one of the supported PvP tag plugins to be installed (all are soft-dependencies): +[CombatLogX](https://www.spigotmc.org/resources/combatlogx.31689/), +[PvPManager](https://modrinth.com/plugin/pvpmanager), or +[Simple Combat Log](https://modrinth.com/plugin/simple-combatlog). + +| Key | Default | Description | +| :--- | :--- | :--- | +| `pvp-tag-integration.cancel-countdown-on-pvp-tag` | `true` | Cancel an active countdown the moment the player receives a PvP tag. | +| `pvp-tag-integration.cancel-queued-on-pvp-tag` | `true` | Skip a queued teleport if the player is already in combat when their slot is dispatched. | + +```yml +pvp-tag-integration: + cancel-countdown-on-pvp-tag: true + cancel-queued-on-pvp-tag: true +``` + +If no supported PvP tag plugin is installed the section is silently ignored. +See the [PvP Tag integration guide](../integrations/pvp-tag) for setup details. + +--- + ## Particles (arrival effect) An optional particle burst played at the destination when the player arrives. diff --git a/docs/integrations/index.md b/docs/integrations/index.md index 8482760..2611753 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -8,3 +8,12 @@ has_children: true EzRTP integrates with several popular plugins. Browse the pages below to set up each one. + +| Page | Plugins covered | +| :--- | :--- | +| [Vault / Economy](vault-economy) | Vault, EzEconomy | +| [PlaceholderAPI](placeholderapi) | PlaceholderAPI | +| [Chunky](chunky) | Chunky (world pre-generation) | +| [Protection](protection-worldguard-griefprevention) | WorldGuard, GriefPrevention, TeamsAPI | +| [PvP Tag](pvp-tag) | CombatLogX, PvPManager, Simple Combat Log | +| [Network / Proxy](network-proxy) | BungeeCord / Velocity proxy destinations | diff --git a/docs/integrations/pvp-tag.md b/docs/integrations/pvp-tag.md new file mode 100644 index 0000000..bd5b03b --- /dev/null +++ b/docs/integrations/pvp-tag.md @@ -0,0 +1,72 @@ +--- +title: PvP Tag (CombatLogX / PvPManager / Simple Combat Log) +nav_order: 5 +parent: Integrations +--- + +# PvP Tag Integration + +Use this integration when you want EzRTP to cancel a pending teleport or active +countdown the moment a player enters combat. + +## Supported plugins + +All three plugins are optional soft-dependencies. EzRTP auto-detects whichever +ones are present and registers them at startup. You can have more than one +installed simultaneously. + +| Plugin | Where to get it | +| :--- | :--- | +| [CombatLogX](https://www.spigotmc.org/resources/combatlogx.31689/) | SpigotMC (597 K+ downloads) | +| [PvPManager](https://modrinth.com/plugin/pvpmanager) | Modrinth · [SpigotMC (free)](https://www.spigotmc.org/resources/pvpmanager-lite.845/) | +| [Simple Combat Log](https://modrinth.com/plugin/simple-combatlog) | Modrinth · [GitHub](https://github.com/NikeyV1/CombatLog) | + +No configuration change is required on servers that do not use any of them. + +## What this integration does + +- **Countdown cancellation** — if a player receives a PvP tag while a countdown is + ticking, the teleport is cancelled and a configurable message is sent. +- **Queue cancellation** — if a queued teleport slot is dispatched while the player + is already tagged, the teleport is skipped and the player must re-queue. + +## Where to configure it + +File: `plugins/EzRTP/rtp.yml` + +```yml +pvp-tag-integration: + cancel-countdown-on-pvp-tag: true + cancel-queued-on-pvp-tag: true +``` + +### Key settings + +- `cancel-countdown-on-pvp-tag` + - `true`: cancel the active countdown as soon as the player is tagged. + - `false`: ignore combat tags during countdown (teleport proceeds regardless). +- `cancel-queued-on-pvp-tag` + - `true`: skip the queued teleport if the player is in combat at dispatch time. + - `false`: dispatch the queued teleport even while in combat. + +## Messages + +Add or override these keys in `plugins/EzRTP/messages/en.yml`: + +| Key | Default text | +| :--- | :--- | +| `countdown-pvp-cancel` | `Teleport cancelled — you entered combat!` | +| `queue-pvp-tag-cancel` | `Teleport skipped — you are in combat!` | + +## Startup log + +When a supported plugin is detected, EzRTP logs a confirmation on enable: + +```text +[EzRTP] PvP tag integration: CombatLogX detected. +[EzRTP] PvP tag integration: PvPManager detected. +[EzRTP] PvP tag integration: Simple Combat Log detected. +``` + +If none of the supported plugins are installed, no message is printed and the +feature is effectively disabled. diff --git a/ezrtp-api/pom.xml b/ezrtp-api/pom.xml index e6d10ac..9939a2a 100644 --- a/ezrtp-api/pom.xml +++ b/ezrtp-api/pom.xml @@ -5,13 +5,13 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml com.skyblockexp ezrtp-api - 3.2.3 + 3.3.0 jar EzRTP API Lightweight public API for EzRTP intended for third-party plugins. diff --git a/ezrtp-bukkit/pom.xml b/ezrtp-bukkit/pom.xml index d69c0cf..f81da51 100644 --- a/ezrtp-bukkit/pom.xml +++ b/ezrtp-bukkit/pom.xml @@ -5,13 +5,13 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml com.skyblockexp ezrtp-bukkit - 3.2.3 + 3.3.0 jar EzRTP (Bukkit) EzRTP Bukkit-compatible plugin module. @@ -111,7 +111,7 @@ com.skyblockexp ezrtp-common - 3.2.3 + 3.3.0 diff --git a/ezrtp-common/pom.xml b/ezrtp-common/pom.xml index c94f377..5a23887 100644 --- a/ezrtp-common/pom.xml +++ b/ezrtp-common/pom.xml @@ -5,13 +5,13 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml com.skyblockexp ezrtp-common - 3.2.3 + 3.3.0 jar EzRTP Common Shared utilities for EzRTP (platform-independent) @@ -89,6 +89,34 @@ 7.0.2 + + + com.github.sirblobman.api + core + 2.9-SNAPSHOT + provided + + + com.github.sirblobman.combatlogx + api + 11.6-SNAPSHOT + provided + + + + me.chancesd.pvpmanager + pvpmanager + 4.0.8 + provided + + + + com.github.NikeyV1 + CombatLog + master-SNAPSHOT + provided + + org.junit.jupiter junit-jupiter diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/bootstrap/EzRtpPluginBootstrap.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/bootstrap/EzRtpPluginBootstrap.java index 0573433..d797e9a 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/bootstrap/EzRtpPluginBootstrap.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/bootstrap/EzRtpPluginBootstrap.java @@ -39,6 +39,7 @@ import org.bukkit.plugin.Plugin; import com.skyblockexp.ezrtp.teleport.ChunkyProvider; import com.skyblockexp.ezrtp.teleport.ChunkyRuntimeProvider; +import com.skyblockexp.ezrtp.pvptag.PvpTagService; // Chunky API is optional; use runtime loader via ChunkyRuntimeProvider import java.io.File; @@ -86,6 +87,7 @@ public final class EzRtpPluginBootstrap { private UsageResetScheduler usageResetScheduler; private final HeatmapSimulationStore heatmapSimulationStore = new HeatmapSimulationStore(); private com.skyblockexp.ezrtp.teleport.ChunkyWarmupCoordinator chunkyWarmupCoordinator; + private PvpTagService pvpTagService; public EzRtpPluginBootstrap(EzRtpPlugin plugin) { this.plugin = plugin; @@ -238,6 +240,24 @@ public void reloadPluginConfiguration() { plugin.getLogger().info("Chunky integration disabled in configuration."); chunkyAPI = null; } + + // Initialize PvP tag providers once; registered only when the corresponding plugin is present. + if (pvpTagService == null) { + pvpTagService = new PvpTagService(); + if (plugin.getServer().getPluginManager().isPluginEnabled("CombatLogX")) { + pvpTagService.registerProvider(new com.skyblockexp.ezrtp.pvptag.CombatLogXPvpTagProvider()); + plugin.getLogger().info("PvP tag integration: CombatLogX detected."); + } + if (plugin.getServer().getPluginManager().isPluginEnabled("PvPManager")) { + pvpTagService.registerProvider(new com.skyblockexp.ezrtp.pvptag.PvpManagerPvpTagProvider()); + plugin.getLogger().info("PvP tag integration: PvPManager detected."); + } + if (plugin.getServer().getPluginManager().isPluginEnabled("CombatLog")) { + pvpTagService.registerProvider(new com.skyblockexp.ezrtp.pvptag.SimpleCombatLogPvpTagProvider()); + plugin.getLogger().info("PvP tag integration: Simple Combat Log detected."); + } + } + messageProvider = configurationService.getMessageProvider(); // Initialize text rendering settings (force legacy conversion for older clients) boolean forceLegacy = configurationService.getEffectiveBaseConfiguration().getBoolean("messages.force-legacy-colors", false); @@ -272,7 +292,7 @@ public void reloadPluginConfiguration() { teleportService = new RandomTeleportService(plugin, defaultSettings, configuration.getQueueSettings(), economyService, (player, settings) -> configuration.resolveTeleportCost(player, settings), - protectionRegistry, messageProvider, ChunkLoadStrategyRegistry.get(), PlatformRuntimeRegistry.get(), chunkyAPI, chunkyWarmupCoordinator); + protectionRegistry, messageProvider, ChunkLoadStrategyRegistry.get(), PlatformRuntimeRegistry.get(), chunkyAPI, chunkyWarmupCoordinator, pvpTagService); try { EzRtpAPI.registerProvider(plugin, teleportService); } catch (Throwable ignored) {} } else { teleportService.reload(defaultSettings, configuration.getQueueSettings()); diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/RandomTeleportSettings.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/RandomTeleportSettings.java index f5aa88e..1c8a2b7 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/RandomTeleportSettings.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/RandomTeleportSettings.java @@ -4,6 +4,7 @@ import com.skyblockexp.ezrtp.config.biome.BiomePreCacheSettings; import com.skyblockexp.ezrtp.config.biome.BiomeSearchSettings; import com.skyblockexp.ezrtp.config.biome.ChunkyIntegrationSettings; +import com.skyblockexp.ezrtp.config.pvptag.PvpTagIntegrationSettings; import com.skyblockexp.ezrtp.config.biome.RareBiomeOptimizationSettings; import com.skyblockexp.ezrtp.config.effects.CountdownBossBarSettings; import com.skyblockexp.ezrtp.config.effects.CountdownParticleSettings; @@ -61,6 +62,7 @@ public final class RandomTeleportSettings { private final boolean cancelOnMove; private final double cancelDistance; private final double warnDistance; + private final PvpTagIntegrationSettings pvpTagIntegrationSettings; public RandomTeleportSettings(ConfigurationSection configSection, String worldName, int centerX, int centerZ, int minimumRadius, int maximumRadius, @@ -92,7 +94,8 @@ public RandomTeleportSettings(ConfigurationSection configSection, minY, maxY, biomeInclude, biomeExclude, protectionSettings, preCacheSettings, rareBiomeOptimizationSettings, chunkLoadingSettings, enableFallbackToCache, biomeSearchSettings, biomeFilteringEnabled, biomeSystemEnabled, safetySettings, - searchPattern, chunkyIntegrationSettings, false, true, 2.0, 1.0); + searchPattern, chunkyIntegrationSettings, false, true, 2.0, 1.0, + PvpTagIntegrationSettings.defaults()); } public RandomTeleportSettings(ConfigurationSection configSection, @@ -126,7 +129,8 @@ public RandomTeleportSettings(ConfigurationSection configSection, minY, maxY, biomeInclude, biomeExclude, protectionSettings, preCacheSettings, rareBiomeOptimizationSettings, chunkLoadingSettings, enableFallbackToCache, biomeSearchSettings, biomeFilteringEnabled, biomeSystemEnabled, safetySettings, - searchPattern, chunkyIntegrationSettings, suppressPlayerMessages, true, 2.0, 1.0); + searchPattern, chunkyIntegrationSettings, suppressPlayerMessages, true, 2.0, 1.0, + PvpTagIntegrationSettings.defaults()); } public RandomTeleportSettings(ConfigurationSection configSection, @@ -155,7 +159,8 @@ public RandomTeleportSettings(ConfigurationSection configSection, boolean suppressPlayerMessages, boolean cancelOnMove, double cancelDistance, - double warnDistance) { + double warnDistance, + PvpTagIntegrationSettings pvpTagIntegrationSettings) { this.configSection = configSection; this.worldName = worldName; this.centerX = centerX; @@ -193,6 +198,8 @@ public RandomTeleportSettings(ConfigurationSection configSection, this.cancelOnMove = cancelOnMove; this.cancelDistance = Math.max(0.0, cancelDistance); this.warnDistance = Math.max(0.0, warnDistance); + this.pvpTagIntegrationSettings = + pvpTagIntegrationSettings != null ? pvpTagIntegrationSettings : PvpTagIntegrationSettings.defaults(); } public Integer getMinY() { return minY; } public Integer getMaxY() { return maxY; } @@ -234,6 +241,8 @@ public RandomTeleportSettings(ConfigurationSection configSection, public ChunkyIntegrationSettings getChunkyIntegrationSettings() { return chunkyIntegrationSettings; } + public PvpTagIntegrationSettings getPvpTagIntegrationSettings() { return pvpTagIntegrationSettings; } + /** Returns {@code true} only when {@code heatmap.enabled: true} is explicitly set in the world's rtp.yml section. */ public boolean isHeatmapEnabled() { return configSection != null && configSection.getBoolean("heatmap.enabled", false); @@ -263,7 +272,7 @@ public RandomTeleportSettings withWorldName(String newWorldName) { chunkLoadingSettings, enableFallbackToCache, biomeSearchSettings, biomeFilteringEnabled, biomeSystemEnabled, safetySettings, searchPattern, chunkyIntegrationSettings, suppressPlayerMessages, - cancelOnMove, cancelDistance, warnDistance); + cancelOnMove, cancelDistance, warnDistance, pvpTagIntegrationSettings); } public RandomTeleportSettings withSuppressPlayerMessages(boolean suppress) { @@ -278,7 +287,7 @@ public RandomTeleportSettings withSuppressPlayerMessages(boolean suppress) { chunkLoadingSettings, enableFallbackToCache, biomeSearchSettings, biomeFilteringEnabled, biomeSystemEnabled, safetySettings, searchPattern, chunkyIntegrationSettings, suppress, - cancelOnMove, cancelDistance, warnDistance); + cancelOnMove, cancelDistance, warnDistance, pvpTagIntegrationSettings); } public static RandomTeleportSettings fromConfiguration(ConfigurationSection section, java.util.logging.Logger logger) { @@ -306,7 +315,8 @@ public static RandomTeleportSettings fromConfiguration(ConfigurationSection sect false, true, 2.0, - 1.0); + 1.0, + PvpTagIntegrationSettings.defaults()); } String worldName = section.getString("world", "world"); @@ -425,6 +435,9 @@ public static RandomTeleportSettings fromConfiguration(ConfigurationSection sect ? countdownSection.getDouble("warn-distance", 1.0) : 1.0; + PvpTagIntegrationSettings pvpTagIntegrationSettings = PvpTagIntegrationSettings.fromConfiguration( + section.getConfigurationSection("pvp-tag-integration"), PvpTagIntegrationSettings.defaults()); + return new RandomTeleportSettings(section, worldName, centerX, centerZ, minRadius, maxRadius, maxAttempts, useWorldBorder, unsafeBlocks, messages, particleSettings, onJoinTeleportSettings, countdownBossBarSettings, countdownParticleSettings, teleportCost, countdownSeconds, countdownChatMessagesEnabled, debugRejectionLogging, minY, maxY, @@ -443,7 +456,8 @@ public static RandomTeleportSettings fromConfiguration(ConfigurationSection sect false, cancelOnMove, cancelDistance, - warnDistance); + warnDistance, + pvpTagIntegrationSettings); } public static RandomTeleportSettings fromConfiguration(ConfigurationSection section, java.util.logging.Logger logger, RandomTeleportSettings fallback) { @@ -597,6 +611,10 @@ public static RandomTeleportSettings fromConfiguration(ConfigurationSection sect ? countdownSection.getDouble("warn-distance", 1.0) : (fallback != null ? fallback.getWarnDistance() : 1.0); + PvpTagIntegrationSettings pvpTagIntegrationSettings = PvpTagIntegrationSettings.fromConfiguration( + section.getConfigurationSection("pvp-tag-integration"), + fallback != null ? fallback.getPvpTagIntegrationSettings() : PvpTagIntegrationSettings.defaults()); + return new RandomTeleportSettings(section, worldName, centerX, centerZ, minRadius, maxRadius, maxAttempts, useWorldBorder, unsafeBlocks, messages, particleSettings, onJoinTeleportSettings, countdownBossBarSettings, countdownParticleSettings, teleportCost, countdownSeconds, countdownChatMessagesEnabled, debugRejectionLogging, minY, maxY, @@ -615,7 +633,8 @@ public static RandomTeleportSettings fromConfiguration(ConfigurationSection sect fallback != null && fallback.isSuppressPlayerMessages(), cancelOnMove, cancelDistance, - warnDistance); + warnDistance, + pvpTagIntegrationSettings); } public int getCountdownSeconds() { return countdownSeconds; diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/pvptag/PvpTagIntegrationSettings.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/pvptag/PvpTagIntegrationSettings.java new file mode 100644 index 0000000..56d714f --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/config/pvptag/PvpTagIntegrationSettings.java @@ -0,0 +1,40 @@ +package com.skyblockexp.ezrtp.config.pvptag; + +import org.bukkit.configuration.ConfigurationSection; + +/** Configuration for PvP-tag integration (PvPManager / CombatLogX). */ +public final class PvpTagIntegrationSettings { + + private final boolean cancelCountdownOnPvpTag; + private final boolean cancelQueuedOnPvpTag; + + public PvpTagIntegrationSettings(boolean cancelCountdownOnPvpTag, boolean cancelQueuedOnPvpTag) { + this.cancelCountdownOnPvpTag = cancelCountdownOnPvpTag; + this.cancelQueuedOnPvpTag = cancelQueuedOnPvpTag; + } + + public boolean isCancelCountdownOnPvpTag() { + return cancelCountdownOnPvpTag; + } + + public boolean isCancelQueuedOnPvpTag() { + return cancelQueuedOnPvpTag; + } + + public static PvpTagIntegrationSettings defaults() { + return new PvpTagIntegrationSettings(true, true); + } + + public static PvpTagIntegrationSettings fromConfiguration( + ConfigurationSection section, PvpTagIntegrationSettings fallback) { + PvpTagIntegrationSettings def = fallback != null ? fallback : defaults(); + if (section == null) { + return def; + } + boolean cancelCountdown = + section.getBoolean("cancel-countdown-on-pvp-tag", def.isCancelCountdownOnPvpTag()); + boolean cancelQueued = + section.getBoolean("cancel-queued-on-pvp-tag", def.isCancelQueuedOnPvpTag()); + return new PvpTagIntegrationSettings(cancelCountdown, cancelQueued); + } +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageKey.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageKey.java index 9d0e2b9..d306afd 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageKey.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageKey.java @@ -20,12 +20,14 @@ public enum MessageKey { // Queue messages QUEUE_QUEUED("queue-queued"), QUEUE_FULL("queue-full"), + QUEUE_PVP_TAG_CANCEL("queue-pvp-tag-cancel"), // Countdown messages COUNTDOWN_START("countdown-start"), COUNTDOWN_TICK("countdown-tick"), COUNTDOWN_MOVE_WARN("countdown-move-warn"), COUNTDOWN_MOVE_CANCEL("countdown-move-cancel"), + COUNTDOWN_PVP_CANCEL("countdown-pvp-cancel"), // Cooldown and usage limit messages COOLDOWN("cooldown"), diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageProvider.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageProvider.java index b496086..b04d32b 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageProvider.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/message/MessageProvider.java @@ -290,6 +290,8 @@ private static String getDefaultMessage(MessageKey key) { case STATS_BIOME_ACTIVITY_HEADER -> "Biomes by Activity (Page /, Total: biomes):"; case STATS_NAVIGATION -> "Navigation:"; case STATS_LEGEND -> "Legend:"; + case COUNTDOWN_PVP_CANCEL -> "Teleport cancelled because you entered combat."; + case QUEUE_PVP_TAG_CANCEL -> "Your queued teleport was cancelled because you entered combat."; }; } } diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/CombatLogXPvpTagProvider.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/CombatLogXPvpTagProvider.java new file mode 100644 index 0000000..3c03960 --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/CombatLogXPvpTagProvider.java @@ -0,0 +1,40 @@ +package com.skyblockexp.ezrtp.pvptag; + +import com.github.sirblobman.combatlogx.api.ICombatLogX; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * {@link PvpTagProvider} backed by CombatLogX. + * Only instantiate when the CombatLogX plugin is confirmed to be loaded. + */ +public final class CombatLogXPvpTagProvider implements PvpTagProvider { + + private final ICombatLogX api; + + public CombatLogXPvpTagProvider() { + this.api = (ICombatLogX) Bukkit.getPluginManager().getPlugin("CombatLogX"); + } + + @Override + public String getName() { + return "CombatLogX"; + } + + @Override + public boolean isAvailable() { + return api != null && Bukkit.getPluginManager().isPluginEnabled("CombatLogX"); + } + + @Override + public boolean isInCombat(Player player) { + if (api == null || player == null) { + return false; + } + try { + return api.getCombatManager().isInCombat(player); + } catch (Throwable t) { + return false; + } + } +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpManagerPvpTagProvider.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpManagerPvpTagProvider.java new file mode 100644 index 0000000..d8e7f21 --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpManagerPvpTagProvider.java @@ -0,0 +1,35 @@ +package com.skyblockexp.ezrtp.pvptag; + +import me.chancesd.pvpmanager.player.CombatPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * {@link PvpTagProvider} backed by PvPManager. + * Only instantiate when the PvPManager plugin is confirmed to be loaded. + */ +public final class PvpManagerPvpTagProvider implements PvpTagProvider { + + @Override + public String getName() { + return "PvPManager"; + } + + @Override + public boolean isAvailable() { + return Bukkit.getPluginManager().isPluginEnabled("PvPManager"); + } + + @Override + public boolean isInCombat(Player player) { + if (player == null) { + return false; + } + try { + CombatPlayer combatPlayer = CombatPlayer.get(player); + return combatPlayer != null && combatPlayer.isInCombat(); + } catch (Throwable t) { + return false; + } + } +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagProvider.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagProvider.java new file mode 100644 index 0000000..0b7ed09 --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagProvider.java @@ -0,0 +1,16 @@ +package com.skyblockexp.ezrtp.pvptag; + +import org.bukkit.entity.Player; + +/** Adapter interface for PvP-tag providers (e.g. CombatLogX, PvPManager). */ +public interface PvpTagProvider { + + /** Display name used for logging. */ + String getName(); + + /** Returns {@code true} when the underlying plugin is loaded and enabled. */ + boolean isAvailable(); + + /** Returns {@code true} when {@code player} is currently in combat according to this provider. */ + boolean isInCombat(Player player); +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagService.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagService.java new file mode 100644 index 0000000..65d0e9e --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/PvpTagService.java @@ -0,0 +1,36 @@ +package com.skyblockexp.ezrtp.pvptag; + +import java.util.ArrayList; +import java.util.List; +import org.bukkit.entity.Player; + +/** Aggregates zero or more {@link PvpTagProvider}s and exposes a unified {@code isInCombat} check. */ +public final class PvpTagService { + + private final List providers = new ArrayList<>(); + + public void registerProvider(PvpTagProvider provider) { + providers.add(provider); + } + + /** Returns {@code true} when at least one provider is registered. */ + public boolean isEnabled() { + return !providers.isEmpty(); + } + + /** + * Returns {@code true} when any available provider reports {@code player} as being in combat. + * Returns {@code false} when {@code player} is {@code null} or no providers are registered. + */ + public boolean isInCombat(Player player) { + if (player == null) { + return false; + } + for (PvpTagProvider provider : providers) { + if (provider.isAvailable() && provider.isInCombat(player)) { + return true; + } + } + return false; + } +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/SimpleCombatLogPvpTagProvider.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/SimpleCombatLogPvpTagProvider.java new file mode 100644 index 0000000..f0fce2b --- /dev/null +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/pvptag/SimpleCombatLogPvpTagProvider.java @@ -0,0 +1,40 @@ +package com.skyblockexp.ezrtp.pvptag; + +import de.nikey.combatLog.CombatLog; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * {@link PvpTagProvider} backed by Simple Combat Log (NikeyV1/CombatLog). + * Only instantiate when the plugin is confirmed to be loaded. + * Modrinth: https://modrinth.com/plugin/simple-combatlog + */ +public final class SimpleCombatLogPvpTagProvider implements PvpTagProvider { + + @Override + public String getName() { + return "Simple Combat Log"; + } + + @Override + public boolean isAvailable() { + return Bukkit.getPluginManager().isPluginEnabled("CombatLog"); + } + + @Override + public boolean isInCombat(Player player) { + if (player == null) { + return false; + } + try { + Plugin plugin = Bukkit.getPluginManager().getPlugin("CombatLog"); + if (!(plugin instanceof CombatLog combatLog)) { + return false; + } + return combatLog.getCombatManager().isInCombat(player); + } catch (Throwable t) { + return false; + } + } +} diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/CountdownManager.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/CountdownManager.java index c7637a7..47954c4 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/CountdownManager.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/CountdownManager.java @@ -5,6 +5,7 @@ import com.skyblockexp.ezrtp.config.RandomTeleportSettings; import com.skyblockexp.ezrtp.message.MessageKey; import com.skyblockexp.ezrtp.message.MessageProvider; +import com.skyblockexp.ezrtp.pvptag.PvpTagService; import com.skyblockexp.ezrtp.util.MessageUtil; import org.bukkit.Bukkit; @@ -28,15 +29,25 @@ public final class CountdownManager { private final org.bukkit.plugin.java.JavaPlugin plugin; private final PlatformScheduler scheduler; private final MessageProvider messageProvider; + private final PvpTagService pvpTagService; private final Map countdownBossBars = new HashMap<>(); public CountdownManager( org.bukkit.plugin.java.JavaPlugin plugin, PlatformScheduler scheduler, MessageProvider messageProvider) { + this(plugin, scheduler, messageProvider, null); + } + + public CountdownManager( + org.bukkit.plugin.java.JavaPlugin plugin, + PlatformScheduler scheduler, + MessageProvider messageProvider, + PvpTagService pvpTagService) { this.plugin = plugin; this.scheduler = scheduler; this.messageProvider = messageProvider; + this.pvpTagService = pvpTagService; } /** @@ -100,6 +111,19 @@ private void runCountdown(Player player, RandomTeleportSettings teleportSettings } } + // PvP tag cancellation check + if (pvpTagService != null + && teleportSettings.getPvpTagIntegrationSettings().isCancelCountdownOnPvpTag() + && pvpTagService.isInCombat(player)) { + clearCountdownBossBar(player.getUniqueId()); + if (!teleportSettings.isSuppressPlayerMessages()) { + MessageUtil.send( + player, messageProvider.format(MessageKey.COUNTDOWN_PVP_CANCEL, player)); + } + if (callback != null) callback.accept(false); + return; + } + // Show countdown tick message if (teleportSettings.isCountdownChatMessagesEnabled() && !teleportSettings.isSuppressPlayerMessages()) { com.skyblockexp.ezrtp.util.MessageUtil.send(player, messageProvider.format(MessageKey.COUNTDOWN_TICK, diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/RandomTeleportService.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/RandomTeleportService.java index 3ad7f06..7095ccb 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/RandomTeleportService.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/RandomTeleportService.java @@ -28,6 +28,7 @@ import com.skyblockexp.ezrtp.teleport.biome.WeightedRareBiomeStrategy; import com.skyblockexp.ezrtp.teleport.queue.ChunkLoadQueue; import com.skyblockexp.ezrtp.teleport.queue.TeleportQueueManager; +import com.skyblockexp.ezrtp.pvptag.PvpTagService; import com.skyblockexp.ezrtp.util.DebugFileLogger; import org.bukkit.Bukkit; @@ -65,6 +66,7 @@ public final class RandomTeleportService implements com.skyblockexp.ezrtp.api.Te private final ChunkyProvider chunkyAPI; private final com.skyblockexp.ezrtp.teleport.ChunkyWarmupCoordinator chunkyWarmupCoordinator; private final HotspotStorage hotspotStorage; + private final PvpTagService pvpTagService; private RandomTeleportSettings settings; private TeleportQueueSettings queueSettings; @@ -80,10 +82,12 @@ public RandomTeleportService(org.bukkit.plugin.java.JavaPlugin plugin, ChunkLoadStrategy chunkLoadStrategy, PlatformRuntime platformRuntime, ChunkyProvider chunkyAPI, - com.skyblockexp.ezrtp.teleport.ChunkyWarmupCoordinator chunkyWarmupCoordinator) { + com.skyblockexp.ezrtp.teleport.ChunkyWarmupCoordinator chunkyWarmupCoordinator, + PvpTagService pvpTagService) { this.plugin = plugin; this.chunkyAPI = chunkyAPI; this.chunkyWarmupCoordinator = chunkyWarmupCoordinator; + this.pvpTagService = pvpTagService; this.debugFileLogger = new DebugFileLogger(plugin); this.settings = settings; this.queueSettings = queueSettings != null ? queueSettings : TeleportQueueSettings.disabled(); @@ -102,8 +106,9 @@ public RandomTeleportService(org.bukkit.plugin.java.JavaPlugin plugin, this.biomeFilterExecutor = Executors.newSingleThreadExecutor( r -> new Thread(r, "ezrtp-biome-filter")); this.locationFinder = new LocationFinder(plugin, statistics, biomeCache, rareBiomeRegistry, chunkLoadQueue, locationValidator, searchStrategy, platformRuntime, chunkyAPI, chunkyWarmupCoordinator, biomeFilterExecutor); - this.countdownManager = new CountdownManager(plugin, platformRuntime.scheduler(), messageProvider); + this.countdownManager = new CountdownManager(plugin, platformRuntime.scheduler(), messageProvider, pvpTagService); this.queueManager = new TeleportQueueManager(plugin, platformRuntime.scheduler(), this.queueSettings, messageProvider); + this.queueManager.setPvpTagService(pvpTagService); this.costCalculator = new TeleportCostCalculator(economyService, costResolver); this.teleportExecutor = new TeleportExecutor(plugin, platformRuntime.scheduler(), messageProvider, statistics, costCalculator, countdownManager, locationFinder, queueManager, () -> this.settings); diff --git a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/queue/TeleportQueueManager.java b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/queue/TeleportQueueManager.java index 23ad57d..64f2bbf 100644 --- a/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/queue/TeleportQueueManager.java +++ b/ezrtp-common/src/main/java/com/skyblockexp/ezrtp/teleport/queue/TeleportQueueManager.java @@ -5,6 +5,7 @@ import com.skyblockexp.ezrtp.message.MessageKey; import com.skyblockexp.ezrtp.message.MessageProvider; import com.skyblockexp.ezrtp.platform.PlatformScheduler; +import com.skyblockexp.ezrtp.pvptag.PvpTagService; import com.skyblockexp.ezrtp.teleport.TeleportReason; import org.bukkit.entity.Player; @@ -34,6 +35,7 @@ public interface QueueExecutionHandler { private boolean queueProcessing; private UUID activeTeleportPlayer; private QueueExecutionHandler executionHandler; + private PvpTagService pvpTagService; public TeleportQueueManager( org.bukkit.plugin.java.JavaPlugin plugin, @@ -50,6 +52,10 @@ public void setExecutionHandler(QueueExecutionHandler executionHandler) { this.executionHandler = executionHandler; } + public void setPvpTagService(PvpTagService pvpTagService) { + this.pvpTagService = pvpTagService; + } + /** * Enqueues a teleport request if queuing is enabled and player cannot bypass. * Returns true if enqueued, false if should proceed immediately. @@ -118,6 +124,16 @@ private void dispatchNextTeleport() { if (player == null || !player.isOnline()) { continue; } + if (pvpTagService != null + && next.settings().getPvpTagIntegrationSettings().isCancelQueuedOnPvpTag() + && pvpTagService.isInCombat(player)) { + if (!next.settings().isSuppressPlayerMessages()) { + MessageUtil.send( + player, + messageProvider.format(MessageKey.QUEUE_PVP_TAG_CANCEL, player)); + } + continue; + } activeTeleportPlayer = next.playerId(); executionHandler.execute(player, next.settings(), next.reason(), this::completeQueuedTeleport); return; diff --git a/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/api/EzRtpServiceEndToEndTest.java b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/api/EzRtpServiceEndToEndTest.java index 5bf39ce..97a4abf 100644 --- a/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/api/EzRtpServiceEndToEndTest.java +++ b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/api/EzRtpServiceEndToEndTest.java @@ -67,7 +67,8 @@ void constructService_and_callSafeMethods() throws Exception { chunkLoadStrategy, platformRuntime, chunkyProvider, - new ChunkyWarmupCoordinator() + new ChunkyWarmupCoordinator(), + null ); assertNotNull(service.getStatistics()); diff --git a/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/packaging/MessagesPackagingTest.java b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/packaging/MessagesPackagingTest.java index 30b7ce6..991441f 100644 --- a/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/packaging/MessagesPackagingTest.java +++ b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/packaging/MessagesPackagingTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.net.URL; + import static org.junit.jupiter.api.Assertions.*; /** @@ -16,7 +18,15 @@ public void packagedResourcesUseLocalizedMessages() { // Expect the localized default to be present assertNotNull(loader.getResource("messages/en.yml"), "messages/en.yml should be on the classpath"); - // Expect the legacy top-level messages.yml to be absent - assertNull(loader.getResource("messages.yml"), "legacy top-level messages.yml should not be packaged"); + // Expect the legacy top-level messages.yml to be absent from EzRTP's own compiled output. + // Third-party dependency JARs on the test classpath may contain their own messages.yml, + // so we only fail if the resource originates from a compiled classes directory. + URL legacyResource = loader.getResource("messages.yml"); + if (legacyResource != null) { + String url = legacyResource.toString(); + assertFalse( + url.contains("/classes/") || url.contains("\\classes\\"), + "legacy top-level messages.yml should not be packaged in EzRTP's own output; found at: " + url); + } } } diff --git a/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/pvptag/PvpTagServiceTest.java b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/pvptag/PvpTagServiceTest.java new file mode 100644 index 0000000..1f0448e --- /dev/null +++ b/ezrtp-common/src/test/java/com/skyblockexp/ezrtp/pvptag/PvpTagServiceTest.java @@ -0,0 +1,89 @@ +package com.skyblockexp.ezrtp.pvptag; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.bukkit.entity.Player; +import org.junit.jupiter.api.Test; + +class PvpTagServiceTest { + + @Test + void isEnabled_noProviders_returnsFalse() { + PvpTagService service = new PvpTagService(); + assertFalse(service.isEnabled()); + } + + @Test + void isEnabled_withProvider_returnsTrue() { + PvpTagService service = new PvpTagService(); + service.registerProvider(stubProvider("Test", true, false)); + assertTrue(service.isEnabled()); + } + + @Test + void isInCombat_noProviders_returnsFalse() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + assertFalse(service.isInCombat(player)); + } + + @Test + void isInCombat_nullPlayer_returnsFalse() { + PvpTagService service = new PvpTagService(); + service.registerProvider(stubProvider("Test", true, true)); + assertFalse(service.isInCombat(null)); + } + + @Test + void isInCombat_notInCombat_returnsFalse() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + service.registerProvider(stubProvider("Test", true, false)); + assertFalse(service.isInCombat(player)); + } + + @Test + void isInCombat_inCombat_returnsTrue() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + service.registerProvider(stubProvider("Test", true, true)); + assertTrue(service.isInCombat(player)); + } + + @Test + void isInCombat_unavailableProvider_skipped() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + service.registerProvider(stubProvider("Unavailable", false, true)); + assertFalse(service.isInCombat(player)); + } + + @Test + void isInCombat_firstProviderFalse_secondProviderTrue_returnsTrue() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + service.registerProvider(stubProvider("First", true, false)); + service.registerProvider(stubProvider("Second", true, true)); + assertTrue(service.isInCombat(player)); + } + + @Test + void isInCombat_multipleProviders_allFalse_returnsFalse() { + PvpTagService service = new PvpTagService(); + Player player = mock(Player.class); + service.registerProvider(stubProvider("First", true, false)); + service.registerProvider(stubProvider("Second", true, false)); + assertFalse(service.isInCombat(player)); + } + + private static PvpTagProvider stubProvider(String name, boolean available, boolean inCombat) { + PvpTagProvider provider = mock(PvpTagProvider.class); + when(provider.getName()).thenReturn(name); + when(provider.isAvailable()).thenReturn(available); + when(provider.isInCombat(org.mockito.ArgumentMatchers.any())).thenReturn(inCombat); + return provider; + } +} diff --git a/ezrtp-paper/pom.xml b/ezrtp-paper/pom.xml index 3f06324..e929096 100644 --- a/ezrtp-paper/pom.xml +++ b/ezrtp-paper/pom.xml @@ -5,13 +5,13 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml com.skyblockexp ezrtp-paper - 3.2.3 + 3.3.0 jar EzRTP (Paper) Paper-optimized module for EzRTP @@ -32,7 +32,7 @@ com.skyblockexp ezrtp-common - 3.2.3 + 3.3.0 provided diff --git a/ezrtp-purpur/pom.xml b/ezrtp-purpur/pom.xml index c493da8..6821aca 100644 --- a/ezrtp-purpur/pom.xml +++ b/ezrtp-purpur/pom.xml @@ -5,7 +5,7 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml @@ -18,12 +18,12 @@ com.skyblockexp ezrtp-paper - 3.2.3 + 3.3.0 com.skyblockexp ezrtp-common - 3.2.3 + 3.3.0 provided diff --git a/ezrtp-spigot/pom.xml b/ezrtp-spigot/pom.xml index cf9cec8..48c017b 100644 --- a/ezrtp-spigot/pom.xml +++ b/ezrtp-spigot/pom.xml @@ -5,13 +5,13 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 ../pom.xml com.skyblockexp ezrtp-spigot - 3.2.3 + 3.3.0 jar EzRTP (Spigot) Spigot-optimized module for EzRTP @@ -26,7 +26,7 @@ com.skyblockexp ezrtp-common - 3.2.3 + 3.3.0 diff --git a/pom.xml b/pom.xml index 2b90215..1ac3a15 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.skyblockexp ezrtp-parent - 3.2.3 + 3.3.0 pom EzRTP Parent Aggregator POM for EzRTP multi-module build @@ -97,6 +97,10 @@ jitpack https://jitpack.io + + sirblobman-public + https://nexus.sirblobman.xyz/public/ + diff --git a/src/main/resources/messages/en.yml b/src/main/resources/messages/en.yml index 7370567..a54f9fb 100644 --- a/src/main/resources/messages/en.yml +++ b/src/main/resources/messages/en.yml @@ -169,3 +169,7 @@ stats-cache-info: "Cache Information:" stats-biome-activity-header: "Biomes by Activity (Page /, Total: biomes):" stats-navigation: "Navigation:" stats-legend: "Legend:" + +# PvP tag integration +countdown-pvp-cancel: "Teleport cancelled because you entered combat." +queue-pvp-tag-cancel: "Your queued teleport was cancelled because you entered combat." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7263eb7..e9475da 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -12,6 +12,9 @@ softdepend: - PlaceholderAPI - Chunky - TeamsAPI + - CombatLogX + - PvPManager + - CombatLog commands: rtp: description: Teleport to a random safe location in the configured world. diff --git a/src/main/resources/rtp.yml b/src/main/resources/rtp.yml index eef40de..329c0ff 100644 --- a/src/main/resources/rtp.yml +++ b/src/main/resources/rtp.yml @@ -115,6 +115,13 @@ chunky-integration: max-coordinator-entries: 2000 low-memory-retention-minutes: 10 +# PvP tag integration — cancels teleport when player enters combat +pvp-tag-integration: + # Cancel active countdown when player is tagged by PvPManager or CombatLogX. + cancel-countdown-on-pvp-tag: true + # Skip a queued teleport when the player is already in combat at dispatch time. + cancel-queued-on-pvp-tag: true + heatmap: enabled: false