GH-414 feat: add spear with lunge cooldown #414
Conversation
…t building against it due to support for older versions Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
|
Second time using #getMethod() so if any mistakes ware made, please make sure to leave review |
There was a problem hiding this comment.
Code Review
This pull request introduces a spear lunge cooldown feature, including the SpearService interface, its implementation SpearServiceImpl, a configuration class SpearSettings, and SpearLungeController which reflectively registers PaperMC's EntityLungeEvent. The review feedback focuses on preventing memory leaks by implementing a removeCooldown mechanism to clean up player data upon disconnect, optimizing map lookups in the cooldown check logic to avoid redundant operations, and ensuring proper event registration for the controller.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
|
@gemini-code-assist why were the version constants removed? |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
read description pls :) because they where left in a commit linked there |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Jakubk15
left a comment
There was a problem hiding this comment.
I reduced reflection usage in my suggestion + I think it would be good to add a bypass permission
| 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()); | ||
| } | ||
| } |
There was a problem hiding this comment.
| 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()); | |
| } | |
| } | |
| 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.Cancellable; | |
| import org.bukkit.event.Event; | |
| import org.bukkit.event.EventHandler; | |
| import org.bukkit.event.EventPriority; | |
| import org.bukkit.event.Listener; | |
| import org.bukkit.event.entity.EntityEvent; | |
| import org.bukkit.event.player.PlayerQuitEvent; | |
| import org.bukkit.plugin.Plugin; | |
| 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.forName("io.papermc.paper.event.entity.EntityLungeEvent").asSubclass(EntityEvent.class); | |
| Bukkit.getPluginManager().registerEvent( | |
| lungeEventClass, | |
| this, | |
| EventPriority.NORMAL, | |
| (listener, event) -> { | |
| if (!settings.lungeCooldown) return; | |
| if (!(event instanceof EntityEvent entityEvent)) return; | |
| if (!(entityEvent.getEntity() instanceof Player player)) return; | |
| UUID uuid = player.getUniqueId(); | |
| if (settings.onlyForFight && !fightManager.isInCombat(uuid)) return; | |
| Duration remaining = spearService.getRemainingCooldown(uuid); | |
| if (!remaining.isZero() && !remaining.isNegative()) { | |
| ((Cancellable) event).setCancelled(true); | |
| noticeService.create() | |
| .player(uuid) | |
| .notice(settings.lungeOnCooldown) | |
| .placeholder("{TIME}", DurationUtil.format(remaining, !settings.useMillis)) | |
| .send(); | |
| } else { | |
| spearService.saveCooldown(uuid); | |
| } | |
| }, | |
| plugin | |
| ); | |
| } catch (ClassNotFoundException e) { | |
| plugin.getLogger().info("EntityLungeEvent not found, skipping spear lunge cooldown registration."); | |
| } | |
| } |
name: Pull Request
about: Submit a contribution to EternalCombat
title: 'GH-{NUMBER} <title>'
labels: []
assignees: []
📝 Description
Add cooldown for using spears with lunge attribute.
Remove static versions of plugins missed in 5645f04
🎯 Type of Change
Test Environment:
runServer,
minecraft 26.1.2
📸 Screenshots (if applicable)