diff --git a/README.md b/README.md index 283a7174..cd1ba228 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,14 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown in action: Combat log anti logout feature +- **Support for newest features** + Our plugin features cooldown and delays for tridents, spears and much more. With **out of the box** configurable feats. + Combat log anti logout feature + - **Customize combat experience** Add custom effects to players in combat or death. Everything should be configurable and user-friendly: Lightning strikes when players die + Flare indicates players death location - **Spawn Protection (Configurable)** diff --git a/assets/spear.gif b/assets/spear.gif new file mode 100644 index 00000000..2680698f Binary files /dev/null and b/assets/spear.gif differ diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 04f20d60..f795e180 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -26,10 +26,6 @@ object Versions { const val PLACEHOLDER_API = "2.12.3" const val LANDS_API = "7.25.4" - const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth - const val PACKETEVENTS = "2.11.1" - const val WORLDGUARD = "7.0.15-beta-01" - const val LUCKPERMS = "5.5.17" } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/spear/SpearService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/spear/SpearService.java new file mode 100644 index 00000000..6e4f39f4 --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/spear/SpearService.java @@ -0,0 +1,11 @@ +package com.eternalcode.combat.fight.spear; + +import java.time.Duration; +import java.util.UUID; + +public interface SpearService { + boolean isOnCooldown(UUID uuid); + void saveCooldown(UUID uuid); + Duration getRemainingCooldown(UUID uuid); + void removeCooldown(UUID uuid); +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java index 4385582c..01abb938 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -48,6 +48,9 @@ import com.eternalcode.combat.fight.pearl.PearlController; import com.eternalcode.combat.fight.pearl.PearlService; import com.eternalcode.combat.fight.pearl.PearlServiceImpl; +import com.eternalcode.combat.fight.spear.SpearLungeController; +import com.eternalcode.combat.fight.spear.SpearService; +import com.eternalcode.combat.fight.spear.SpearServiceImpl; import com.eternalcode.combat.fight.tagout.FightTagOutCommand; import com.eternalcode.combat.fight.tagout.FightTagOutController; import com.eternalcode.combat.fight.tagout.FightTagOutService; @@ -150,6 +153,8 @@ public void onEnable() { BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border); KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider); + SpearService spearService = new SpearServiceImpl(pluginConfig.spear); + this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server) .message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound) .message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers) @@ -214,6 +219,8 @@ public void onEnable() { new KnockbackMountController(noticeService, this.regionProvider, this.fightManager).register(this); + new SpearLungeController(this, fightManager, spearService, pluginConfig.spear, noticeService); + eventManager.subscribe( PlayerDeathEvent.class, pluginConfig.drop.dropEventPriority, diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java index 6534cf0f..7201b268 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java @@ -6,6 +6,7 @@ import com.eternalcode.combat.fight.effect.FightEffectSettings; import com.eternalcode.combat.fight.knockback.KnockbackSettings; import com.eternalcode.combat.fight.pearl.PearlSettings; +import com.eternalcode.combat.fight.spear.SpearSettings; import com.eternalcode.combat.fight.trident.TridentSettings; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; @@ -44,6 +45,13 @@ public class PluginConfig extends OkaeriConfig { }) public TridentSettings trident = new TridentSettings(); + @Comment({ + " ", + "# Settings related to Spears with lunge", + "# Set cooldown for spear lunging" + }) + public SpearSettings spear = new SpearSettings(); + @Comment({ " ", "# Custom effects applied during combat.", diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearLungeController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearLungeController.java new file mode 100644 index 00000000..2d40a556 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearLungeController.java @@ -0,0 +1,85 @@ +package com.eternalcode.combat.fight.spear; + +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.notification.NoticeService; +import com.eternalcode.combat.util.DurationUtil; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Method; +import java.time.Duration; +import java.util.UUID; + +public class SpearLungeController implements Listener { + + private final SpearService spearService; + + public SpearLungeController(Plugin plugin, FightManager fightManager, SpearService spearService, SpearSettings settings, NoticeService noticeService) { + this.spearService = spearService; + + + Bukkit.getPluginManager().registerEvents(this, plugin); + + try { + Class lungeEventClass = (Class) Class.forName("io.papermc.paper.event.entity.EntityLungeEvent"); + + Method getEntityMethod = lungeEventClass.getMethod("getEntity"); + Method setCancelledMethod = lungeEventClass.getMethod("setCancelled", boolean.class); + + Bukkit.getPluginManager().registerEvent( + lungeEventClass, + this, + EventPriority.NORMAL, + (listener, event) -> { + if (!settings.lungeCooldown) return; + if (!lungeEventClass.isInstance(event)) return; + + try { + Object entity = getEntityMethod.invoke(event); + + if (entity instanceof Player player) { + UUID uuid = player.getUniqueId(); + + if (settings.onlyForFight && !fightManager.isInCombat(uuid)) { + return; + } + + // Query the remaining cooldown once to avoid redundant lookups and race conditions + Duration remaining = spearService.getRemainingCooldown(uuid); + + if (!remaining.isZero() && !remaining.isNegative()) { + setCancelledMethod.invoke(event, true); + + noticeService.create() + .player(uuid) + .notice(settings.lungeOnCooldown) + .placeholder("{TIME}", DurationUtil.format(remaining, !settings.useMillis)) + .send(); + } else { + spearService.saveCooldown(uuid); + } + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to handle EntityLungeEvent reflectively: " + e.getMessage()); + } + }, + plugin + ); + } catch (ClassNotFoundException e) { + plugin.getLogger().info("EntityLungeEvent not found, skipping spear lunge cooldown registration."); + } catch (NoSuchMethodException e) { + plugin.getLogger().warning("Failed to find necessary methods for EntityLungeEvent: " + e.getMessage()); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + this.spearService.removeCooldown(event.getPlayer().getUniqueId()); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearServiceImpl.java new file mode 100644 index 00000000..e6f6920a --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearServiceImpl.java @@ -0,0 +1,49 @@ +package com.eternalcode.combat.fight.spear; + +import java.time.Duration; +import java.time.Instant; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class SpearServiceImpl implements SpearService { + + private final SpearSettings settings; + private final Map cooldowns = new ConcurrentHashMap<>(); + + public SpearServiceImpl(SpearSettings settings) { + this.settings = settings; + } + + @Override + public boolean isOnCooldown(UUID uuid) { + return !this.getRemainingCooldown(uuid).isZero(); + } + + @Override + public void saveCooldown(UUID uuid) { + this.cooldowns.put(uuid, Instant.now().plus(this.settings.lungeCooldownDuration)); + } + + @Override + public Duration getRemainingCooldown(UUID uuid) { + Instant expiration = this.cooldowns.get(uuid); + + if (expiration == null) { + return Duration.ZERO; + } + + Instant now = Instant.now(); + if (now.isAfter(expiration)) { + this.cooldowns.remove(uuid); + return Duration.ZERO; + } + + return Duration.between(now, expiration); + } + + @Override + public void removeCooldown(UUID uuid) { + this.cooldowns.remove(uuid); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearSettings.java new file mode 100644 index 00000000..94720e73 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearSettings.java @@ -0,0 +1,29 @@ +package com.eternalcode.combat.fight.spear; + +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.time.Duration; + +public class SpearSettings extends OkaeriConfig { + + @Comment("# Should spears have lunge cooldown or delay between uses? True for cooldown, False for vanilla mechanics") + public boolean lungeCooldown = false; + + @Comment("# Should cooldown be applied only during fights") + public boolean onlyForFight = true; + + @Comment("# Duration of cooldown") + public Duration lungeCooldownDuration = Duration.ofSeconds(5); + + @Comment("# Should milliseconds be used for last second of cooldown for more precise time") + public boolean useMillis = true; + + @Comment({ + "# Notice sent to the players that try to use spears before cooldown ends.", + "# Placeholder: {TIME} - time left of cooldown" + }) + public Notice lungeOnCooldown = Notice.builder().actionBar("Spear cannot be used for next {TIME}") + .build(); + +}