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:
+- **Support for newest features**
+ Our plugin features cooldown and delays for tridents, spears and much more. With **out of the box** configurable feats.
+
+
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
+
- **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 extends Event> lungeEventClass = (Class extends Event>) 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();
+
+}