Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent wind charge exploits on switch blocks. #7652

Merged
merged 1 commit into from
Oct 29, 2024
Merged
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 @@ -60,6 +60,26 @@ public class TownyActionEventExecutor {
* @return true if not cancelled by the cache or the event results.
*/
private static boolean isAllowedAction(Player player, Location loc, Material mat, ActionType action, TownyActionEvent event) {
return isAllowedAction(player, loc, mat, action, event, false);
}

/**
* First checks the Player's cache using the PlayerCacheUtil, and sets the
* cancellation of the Action-Type Event. Then fires the ActionType-based Event,
* with which Towny will decide if Event War or Flag War change anything, or any
* other plugin wanting to affect the outcome of an action-based decision.
*
* Displays feedback to the player when an action is cancelled.
*
* @param player - Player involved in the event.
* @param loc - Location of the event.
* @param mat - Material being involved in the event.
* @param action - The ActionType of the event. ex: BUILD
* @param event - One of the four ActionType-based events.
* @param silent - Whether to suppress the message.
* @return true if not cancelled by the cache or the event results.
*/
private static boolean isAllowedAction(Player player, Location loc, Material mat, ActionType action, TownyActionEvent event, boolean silent) {

/*
* Use the PlayerCache to decide what Towny will do,
Expand All @@ -82,7 +102,7 @@ private static boolean isAllowedAction(Player player, Location loc, Material mat
/*
* Send any feedback when the action is denied.
*/
if (event.isCancelled() && !event.isMessageSuppressed())
if (event.isCancelled() && !event.isMessageSuppressed() && !silent)
TownyMessaging.sendErrorMsg(player, event.getCancelMessage());

return !event.isCancelled();
Expand Down Expand Up @@ -248,8 +268,21 @@ public static boolean canDestroy(Player player, Block block) {
* @return true if allowed.
*/
public static boolean canSwitch(Player player, Location loc, Material mat) {
return canSwitch(player, loc, mat, false);
}

/**
* Can the player use switches of this material at this location?
*
* @param player - Player involved in the event.
* @param loc - Location of the event.
* @param mat - Material being involved in the event.
* @param silent Whether to show an error message.
* @return true if allowed.
*/
public static boolean canSwitch(Player player, Location loc, Material mat, boolean silent) {
TownySwitchEvent event = new TownySwitchEvent(player, loc, mat, getBlock(loc), TownyAPI.getInstance().getTownBlock(loc), false);
return isAllowedAction(player, loc, mat, ActionType.SWITCH, event);
return isAllowedAction(player, loc, mat, ActionType.SWITCH, event, silent);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.common.collect.HashBiMap;
import com.palmergames.bukkit.towny.Towny;
import com.palmergames.bukkit.towny.TownyAPI;
import com.palmergames.bukkit.towny.TownyMessaging;
import com.palmergames.bukkit.towny.TownySettings;
import com.palmergames.bukkit.towny.event.mobs.MobSpawnRemovalEvent;
import com.palmergames.bukkit.towny.hooks.PluginIntegrations;
Expand All @@ -12,13 +13,15 @@
import com.palmergames.bukkit.towny.object.TownBlock;
import com.palmergames.bukkit.towny.object.TownBlockType;
import com.palmergames.bukkit.towny.object.TownyWorld;
import com.palmergames.bukkit.towny.object.Translatable;
import com.palmergames.bukkit.towny.object.WorldCoord;
import com.palmergames.bukkit.towny.regen.TownyRegenAPI;
import com.palmergames.bukkit.towny.regen.block.BlockLocation;
import com.palmergames.bukkit.towny.tasks.MobRemovalTimerTask;
import com.palmergames.bukkit.towny.utils.BorderUtil;
import com.palmergames.bukkit.towny.utils.CombatUtil;
import com.palmergames.bukkit.towny.utils.EntityTypeUtil;
import com.palmergames.bukkit.towny.utils.MinecraftVersion;
import com.palmergames.bukkit.util.BukkitTools;
import com.palmergames.bukkit.util.EntityLists;
import com.palmergames.bukkit.util.ItemLists;
Expand Down Expand Up @@ -50,6 +53,7 @@
import org.bukkit.entity.Trident;
import org.bukkit.entity.Vehicle;
import org.bukkit.entity.Villager;
import org.bukkit.entity.WindCharge;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
Expand Down Expand Up @@ -79,7 +83,9 @@
import org.bukkit.projectiles.ProjectileSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -561,7 +567,10 @@ public void onEntityExplode(EntityExplodeEvent event) {
final TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(event.getEntity().getWorld());
if (townyWorld == null || !townyWorld.isUsingTowny())
return;


if (isWindCharge(event))
return;

List<Block> blocks = TownyActionEventExecutor.filterExplodableBlocks(event.blockList(), null, event.getEntity(), event);
event.blockList().clear();
event.blockList().addAll(blocks);
Expand All @@ -588,6 +597,53 @@ public void onEntityExplode(EntityExplodeEvent event) {
}
}

/**
* Protects against players using Wind Charges to open doors and use switches.
*
* @param event - EntityExplodeEvent
*/
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onWindChargeExplode(EntityExplodeEvent event) {
if (plugin.isError()) {
event.setCancelled(true);
return;
}

final TownyWorld townyWorld = TownyAPI.getInstance().getTownyWorld(event.getEntity().getWorld());
if (townyWorld == null || !townyWorld.isUsingTowny())
return;

if (!isWindCharge(event))
return;

Player player = getWindChargePlayerOrNull(event);
if (player == null)
return;

List<Block> deniedBlocks = new ArrayList<>();
for (Block block : event.blockList())
if (TownySettings.isSwitchMaterial(block.getType(), block.getLocation()) && !TownyActionEventExecutor.canSwitch(player, block.getLocation(), block.getType(), true))
deniedBlocks.add(block);

if (deniedBlocks.isEmpty())
return;

event.blockList().removeAll(deniedBlocks);
TownyMessaging.sendErrorMsg(player, Translatable.of("msg_err_not_allowed_to_switch"));
}

private boolean isWindCharge(EntityExplodeEvent event) {
return MinecraftVersion.CURRENT_VERSION.isNewerThanOrEquals(MinecraftVersion.MINECRAFT_1_21)
&& event.getEntity() instanceof WindCharge charge;
}

@Nullable
private Player getWindChargePlayerOrNull(EntityExplodeEvent event) {
if (event.getEntity() instanceof WindCharge charge && charge.getShooter() instanceof Player player)
return player;
return null;
}

/**
* Prevent fire arrows and charges igniting players when PvP is disabled
* <br>
Expand Down Expand Up @@ -799,14 +855,14 @@ public void onProjectileHitEventButtonOrPlate(ProjectileHitEvent event) {
/*
* Bypass any occasion where there is no block being hit and the shooter isn't a player.
*/
if (plugin.isError() || !TownyAPI.getInstance().isTownyWorld(event.getEntity().getWorld()) || event.getHitBlock() == null || !(event.getEntity().getShooter() instanceof Player))
if (plugin.isError() || !TownyAPI.getInstance().isTownyWorld(event.getEntity().getWorld()) || event.getHitBlock() == null || !(event.getEntity().getShooter() instanceof Player player))
return;

Block block = event.getHitBlock().getRelative(event.getHitBlockFace());
Material material = block.getType();
if (ItemLists.PROJECTILE_TRIGGERED_REDSTONE.contains(material) && TownySettings.isSwitchMaterial(material, block.getLocation())) {
//Make decision on whether this is allowed using the PlayerCache and then a cancellable event.
if (!TownyActionEventExecutor.canSwitch((Player) event.getEntity().getShooter(), block.getLocation(), material)) {
if (!TownyActionEventExecutor.canSwitch(player, block.getLocation(), material)) {
/*
* Since we are unable to cancel a ProjectileHitEvent on buttons &
* pressure plates even using MC 1.17 we must set the block to air
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ private MinecraftVersion() {}
public static final Version MINECRAFT_1_20_2 = Version.fromString("1.20.2");
public static final Version MINECRAFT_1_20_3 = Version.fromString("1.20.3");
public static final Version MINECRAFT_1_20_5 = Version.fromString("1.20.5");
public static final Version MINECRAFT_1_21 = Version.fromString("1.21");

public static final Version CURRENT_VERSION = Version.fromString(Bukkit.getBukkitVersion());
public static final Version OLDEST_VERSION_SUPPORTED = MINECRAFT_1_19_1;
Expand Down
2 changes: 2 additions & 0 deletions Towny/src/main/resources/lang/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2612,3 +2612,5 @@ msg_admin_eco_convert_conversion_progress: "Account conversion in progress, %s c
msg_admin_eco_convert_success: "Economy conversion successful"

msg_err_mode_does_not_exist: "The mode %s is not recognized."

msg_err_not_allowed_to_switch: "You aren't allowed to Switch here."
Loading