Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.eternalcode.combat.fight.drop.impl.PercentDropModifier;
import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier;
import com.eternalcode.combat.fight.blocker.PlaceBlockBlocker;
import com.eternalcode.combat.fight.blocker.SignEditingBlocker;
import com.eternalcode.combat.fight.effect.FightEffectController;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.effect.FightEffectServiceImpl;
Expand Down Expand Up @@ -204,6 +205,7 @@ public void onEnable() {
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new InventoryContainersBlocker(this.fightManager, pluginConfig, noticeService),
new SignEditingBlocker(this.fightManager, pluginConfig),
new CommandsBlocker(this.fightManager, noticeService, pluginConfig),
new ElytraBlocker(this.fightManager, pluginConfig),
new ElytraEquipBlocker(this.fightManager, noticeService, pluginConfig, server),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ public class PluginConfig extends OkaeriConfig {
})
public InventorySettings inventory = new InventorySettings();

@Comment({
" ",
"# Settings related to sign editing during combat.",
"# Created for sign traps where opening the sign editor can block ender pearl throws."
})
public SignEditingSettings signEditing = new SignEditingSettings();

@Comment({
" ",
"# Settings related to placeholders used in the plugin.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.eternalcode.combat.config.implementation;

import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;

public class SignEditingSettings extends OkaeriConfig {

@Comment({
"# Prevent players from editing signs during combat.",
"# Disabled by default; created for sign traps where sign editing blocks ender pearl throws."
})
public boolean disableSignEditingDuringCombat = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.eternalcode.combat.fight.blocker;

import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Event.Result;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;

public class SignEditingBlocker implements Listener {

private final FightManager fightManager;
private final PluginConfig config;

public SignEditingBlocker(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}

@EventHandler
void onInteract(PlayerInteractEvent event) {
if (!this.config.signEditing.disableSignEditingDuringCombat) {
return;
}

if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}

Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null || !isSign(clickedBlock.getType())) {
return;
}

Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();

if (!this.fightManager.isInCombat(uniqueId)) {
return;
}

event.setUseInteractedBlock(Result.DENY);

ItemStack item = event.getItem();
if (item != null && isEnderPearl(item.getType())) {
event.setUseItemInHand(Result.ALLOW);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve prior item-use denials when allowing pearls

When this blocker runs for a combat player holding an ender pearl, it unconditionally changes the item result to ALLOW. PlayerInteractEvent can already have an item-use DENY from an earlier listener (for example a protected region or item blacklist), and this line overrides that denial on sign clicks, letting the pearl launch where another plugin intentionally blocked it. Only force ALLOW if the current item result is not DENY, or otherwise preserve existing cancellation state.

Useful? React with 👍 / 👎.

}
}
Comment on lines +26 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To prevent potential bypasses of region protections (e.g., WorldGuard, Towny) and other internal plugin features (like Ender Pearl cooldowns/restrictions), we should:

  1. Add ignoreCancelled = true to the @EventHandler annotation. This ensures the blocker does not run if the interaction event has already been cancelled by a protection plugin.
  2. Check if event.useItemInHand() is already set to Result.DENY before setting it to Result.ALLOW. This prevents overriding other controllers (like a pearl cooldown controller) that have explicitly blocked the item use.
    @EventHandler(ignoreCancelled = true)
    void onInteract(PlayerInteractEvent event) {
        if (!this.config.signEditing.disableSignEditingDuringCombat) {
            return;
        }

        if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
            return;
        }

        Block clickedBlock = event.getClickedBlock();
        if (clickedBlock == null || !isSign(clickedBlock.getType())) {
            return;
        }

        Player player = event.getPlayer();
        UUID uniqueId = player.getUniqueId();

        if (!this.fightManager.isInCombat(uniqueId)) {
            return;
        }

        event.setUseInteractedBlock(Result.DENY);

        ItemStack item = event.getItem();
        if (item != null && isEnderPearl(item.getType()) && event.useItemInHand() != Result.DENY) {
            event.setUseItemInHand(Result.ALLOW);
        }
    }


static boolean isSign(Material material) {
return material.name().endsWith("_SIGN");
}

static boolean isEnderPearl(Material material) {
return material == Material.ENDER_PEARL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.eternalcode.combat.config.implementation;

import static org.junit.jupiter.api.Assertions.assertFalse;

import org.junit.jupiter.api.Test;

class SignEditingSettingsTest {

@Test
void shouldDisableSignEditingBlockerByDefault() {
SignEditingSettings signEditingSettings = new SignEditingSettings();

assertFalse(signEditingSettings.disableSignEditingDuringCombat);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.eternalcode.combat.fight.blocker;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.bukkit.Material;
import org.junit.jupiter.api.Test;

class SignEditingBlockerTest {

@Test
void shouldRecognizeStandingSignMaterials() {
assertTrue(SignEditingBlocker.isSign(Material.OAK_SIGN));
assertTrue(SignEditingBlocker.isSign(Material.CRIMSON_SIGN));
}

@Test
void shouldRecognizeWallSignMaterials() {
assertTrue(SignEditingBlocker.isSign(Material.OAK_WALL_SIGN));
assertTrue(SignEditingBlocker.isSign(Material.WARPED_WALL_SIGN));
}

@Test
void shouldIgnoreNonSignMaterials() {
assertFalse(SignEditingBlocker.isSign(Material.CHEST));
assertFalse(SignEditingBlocker.isSign(Material.ENDER_PEARL));
}

@Test
void shouldRecognizeEnderPearlMaterial() {
assertTrue(SignEditingBlocker.isEnderPearl(Material.ENDER_PEARL));
assertFalse(SignEditingBlocker.isEnderPearl(Material.OAK_SIGN));
}
}
Loading