Compare commits

..

15 Commits

Author SHA1 Message Date
YouHaveTrouble d013eabd02 check for worldguard force pvp region within pvp check 2025-02-28 12:08:06 +01:00
YouHaveTrouble df20c95e38 bump version 2024-11-01 12:37:20 +01:00
YouHaveTrouble c6f0df5d50 account for more edge cases and improve checknig for protected entities 2024-11-01 12:37:06 +01:00
YouHaveTrouble 6a5e5f8d44 fix worldguard hook failing to initialize 2024-10-28 19:52:35 +01:00
YouHaveTrouble ee3526c55d bump version 2024-07-04 21:43:34 +02:00
YouHaveTrouble 2189616a9e fix dangerous block blocker not having list of dangerous blocks 2024-07-04 21:43:19 +02:00
YouHaveTrouble 1484d262c0 tell paper to not remap 2024-06-26 18:24:55 +02:00
YouHaveTrouble 4976ff56f0 check base potion type for nulls 2024-06-26 18:22:15 +02:00
YouHaveTrouble 477f042e91 update depends to java 21 and minecraft 1.21 2024-06-26 18:21:54 +02:00
YouHaveTrouble 409d115337 stop specified commands from being able to execute in combat 2024-05-12 14:17:59 +02:00
YouHaveTrouble 6c022dd88e bump version 2024-04-18 17:14:42 +02:00
YouHaveTrouble 01b6cd309c Decrease event handling priority in EnvironmentalListener 2024-04-18 17:12:46 +02:00
YouHaveTrouble 7253eab958 Lower event handler priority in PvpListener 2024-04-18 17:11:33 +02:00
YouHaveTrouble 645068f66c bump version 2024-04-18 15:17:59 +02:00
YouHaveTrouble 27a68339ed Add null checks for playerData in PlayerListener
Added null checks for playerData in the methods onPlayerQuit, onPlayerTeleport and onPlayerDeath of the PlayerListener class. This addresses potential issues where plugins are using players as entities, which can sometimes cause playerData to be null.
2024-04-18 15:16:14 +02:00
10 changed files with 167 additions and 49 deletions
+11 -4
View File
@@ -6,14 +6,14 @@
<groupId>me.youhavetrouble</groupId>
<artifactId>PreventStabby</artifactId>
<version>2.0.0-pre-1</version>
<version>2.0.0-rc-3</version>
<packaging>jar</packaging>
<name>PreventStabby</name>
<description>Stop people from getting stabbed!</description>
<properties>
<java.version>17</java.version>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -31,7 +31,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -46,6 +46,13 @@
<shadedPattern>me.youhavetrouble.preventstabby.bstats</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<paperweight-mappings-namespace>mojang</paperweight-mappings-namespace>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
@@ -78,7 +85,7 @@
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
@@ -11,11 +11,16 @@ import me.youhavetrouble.preventstabby.listeners.PvpListener;
import me.youhavetrouble.preventstabby.listeners.UtilListener;
import me.youhavetrouble.preventstabby.util.*;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Tameable;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.UUID;
public final class PreventStabby extends JavaPlugin {
@@ -57,17 +62,37 @@ public final class PreventStabby extends JavaPlugin {
}
Metrics metrics = new Metrics(this, 14074);
getServer().getOnlinePlayers().forEach(player -> playerManager.getPlayerData(player.getUniqueId()));
getServer().getWorlds().forEach(world -> {
for (Chunk chunk : world.getLoadedChunks()) {
if (!chunk.isEntitiesLoaded()) continue;
Bukkit.getRegionScheduler().run(plugin, chunk.getWorld(), chunk.getX(), chunk.getZ(), (task) -> {
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof Tameable tameable)) continue;
UUID ownerId = tameable.getOwnerUniqueId();
if (ownerId == null) continue;
getPlayerManager().getPlayerData(ownerId).thenAccept(playerData -> {
if (playerData == null) return;
playerData.addRelatedEntity(entity.getUniqueId());
});
}
});
}
});
}
@Override
public void onLoad() {
if (getServer().getPluginManager().getPlugin("WorldGuard") != null) {
try {
WorldGuardHook.init();
WorldGuardHook.init(this.getLogger());
worldGuardHook = true;
} catch (NoClassDefFoundError e) {
worldGuardHook = false;
}
} else {
worldGuardHook = false;
}
}
@@ -1,16 +1,14 @@
package me.youhavetrouble.preventstabby.config;
import me.youhavetrouble.preventstabby.PreventStabby;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
public class ConfigCache {
@@ -36,6 +34,7 @@ public class ConfigCache {
public final double combat_time, login_protection_time, teleport_protection_time, bucket_stopper_radius,
fire_stopper_radius, block_stopper_radius;
private final Set<String> combatBlockedCommands = new HashSet<>();
private final Set<Material> dangerousBlocks = new HashSet<>();
private final FileConfiguration config;
@@ -66,12 +65,14 @@ public class ConfigCache {
List.of("Should killing of a player that logged out of combat be announced?")
);
this.combatBlockedCommands.addAll(getList(
List<String> commandsBlockedInCombat = getList(
"settings.block_in_combat.commands",
List.of("spawn", "tpa", "home"),
List.of("Commands to block when player is in combat")
)
);
for (String command : commandsBlockedInCombat) {
this.combatBlockedCommands.add(command.toLowerCase(Locale.ENGLISH));
}
this.block_teleports_in_combat = getBoolean(
"settings.block_in_combat.teleports",
@@ -125,6 +126,21 @@ public class ConfigCache {
2.5,
List.of("Distance from the player where placing dangerous blocks will be disallowed")
);
List<String> rawDangerousBlocks = getList(
"settings.environmental.block_stopper.blocks",
List.of("tnt", "magma_block", "cactus", "campfire"),
List.of("List of dangerous blocks that will be blocked when placed near players with pvp off")
);
for (String block : rawDangerousBlocks) {
Material material = Material.matchMaterial(block);
if (material != null) {
dangerousBlocks.add(material);
} else {
plugin.getLogger().warning("Invalid material: " + block);
}
}
// Messages
this.pvp_enabled = getString("messages.pvp_enabled", "<red>You enabled PvP!");
@@ -174,6 +190,10 @@ public class ConfigCache {
return Collections.unmodifiableSet(combatBlockedCommands);
}
public Set<Material> getDangerousBlocks() {
return Collections.unmodifiableSet(dangerousBlocks);
}
private String getString(String path, @NotNull String def) {
return getString(path, def, null);
}
@@ -1,7 +1,10 @@
package me.youhavetrouble.preventstabby.data;
import me.youhavetrouble.preventstabby.PreventStabby;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
@@ -13,6 +16,7 @@ public class PlayerData {
private long lastAccessTimestamp;
private Long combatStartTimestamp, teleportTimestamp, loginTimestamp;
private boolean pvpEnabled, lastCombatCheck;
private final Set<UUID> relatedEntities = new HashSet<>();
public PlayerData(UUID playerUuid, boolean pvpEnabled) {
this.playerUuid = playerUuid;
@@ -106,6 +110,7 @@ public class PlayerData {
* Retrieves the login timestamp for the player.
* @return The login timestamp for the player.
*/
@Nullable
public Long getLoginTimestamp() {
return loginTimestamp;
}
@@ -162,4 +167,28 @@ public class PlayerData {
public boolean isProtected() {
return hasLoginProtection() || hasTeleportProtection();
}
/**
* Get the list of entities related to the player that should keep player's data loaded
* @return The list of entities related to the player that should keep player's data loaded
*/
public Set<UUID> getRelatedEntities() {
return relatedEntities;
}
/**
* @see #getRelatedEntities()
* @param entity The entity id to add to the list of related entities
*/
public void addRelatedEntity(UUID entity) {
relatedEntities.add(entity);
}
/**
* @see #getRelatedEntities()
* @param entity The entity id to remove from the list of related entities
*/
public void removeRelatedEntity(UUID entity) {
relatedEntities.remove(entity);
}
}
@@ -46,6 +46,7 @@ public class PlayerListener implements Listener {
Player player = event.getPlayer();
if (!PreventStabby.getPlugin().getConfigCache().punish_for_combat_logout) return;
PlayerData playerData = PreventStabby.getPlugin().getPlayerManager().getPlayer(player.getUniqueId());
if (playerData == null) return;
if (!playerData.isInCombat()) return;
player.setHealth(0);
if (!PreventStabby.getPlugin().getConfigCache().punish_for_combat_logout_announce) return;
@@ -56,6 +57,7 @@ public class PlayerListener implements Listener {
public void onPlayerTeleport(PlayerTeleportEvent event) {
Player player = event.getPlayer();
PlayerData playerData = PreventStabby.getPlugin().getPlayerManager().getPlayer(player.getUniqueId());
if (playerData == null) return; // Plugins using players as entities can cause this state
playerData.setTeleportTimestamp(Instant.now().getEpochSecond());
}
@@ -63,7 +65,7 @@ public class PlayerListener implements Listener {
public void onPlayerDeath(EntityDeathEvent event) {
if (!(event.getEntity() instanceof Player player)) return;
PlayerData playerData = PreventStabby.getPlugin().getPlayerManager().getPlayer(player.getUniqueId());
if (playerData == null) return;
if (playerData == null) return; // Plugins using players as entities can cause this state
playerData.markNotInCombat();
}
@@ -76,7 +78,10 @@ public class PlayerListener implements Listener {
if (!(entity instanceof Tameable tameable)) return;
UUID ownerId = tameable.getOwnerUniqueId();
if (ownerId == null) return;
PreventStabby.getPlugin().getPlayerManager().getPlayerData(ownerId);
PreventStabby.getPlugin().getPlayerManager().getPlayerData(ownerId).thenAccept(playerData -> {
if (playerData == null) return;
playerData.addRelatedEntity(entity.getUniqueId());
});
}
}
@@ -3,14 +3,16 @@ package me.youhavetrouble.preventstabby.data;
import me.youhavetrouble.preventstabby.PreventStabby;
import me.youhavetrouble.preventstabby.api.event.PlayerEnterCombatEvent;
import me.youhavetrouble.preventstabby.api.event.PlayerLeaveCombatEvent;
import me.youhavetrouble.preventstabby.hooks.WorldGuardHook;
import me.youhavetrouble.preventstabby.util.PluginMessages;
import me.youhavetrouble.preventstabby.util.PvpState;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
@@ -29,25 +31,29 @@ public class PlayerManager {
playerList.values().removeIf(PlayerData::isCacheExpired);
}, 250, 250, TimeUnit.MILLISECONDS);
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, (task) -> Bukkit.getWorlds().forEach((world -> {
for (Chunk chunk : world.getLoadedChunks()) {
if (!chunk.isEntitiesLoaded()) continue;
Bukkit.getRegionScheduler().run(plugin, chunk.getWorld(), chunk.getX(), chunk.getZ(), (task1) -> {
for (Entity entity : chunk.getEntities()) {
if (!(entity instanceof Tameable tameable)) continue;
UUID ownerId = tameable.getOwnerUniqueId();
if (ownerId == null) continue;
getPlayerData(ownerId);
}
});
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, (task) -> {
Collection<? extends Player> players = new ArrayList<>(Bukkit.getOnlinePlayers());
// Load data for all online players as a failsafe
CompletableFuture.allOf(players.stream().map(player -> getPlayerData(player.getUniqueId())).toArray(CompletableFuture[]::new)).join();
// Refresh cache time for all players
for (PlayerData playerData : playerList.values()) {
if (playerData == null) continue;
playerData.refreshCacheTime();
}
})), 5, 20 * 15);
}, 1, 1, TimeUnit.SECONDS);
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, (task) -> {
for (PlayerData playerData : playerList.values()) {
if (playerData == null) continue;
Player player = Bukkit.getPlayer(playerData.getPlayerUuid());
if (player == null || !player.isOnline()) continue;
if (player == null || !player.isOnline()) {
// player not online, so check for related entities
playerData.getRelatedEntities().removeIf( uuid -> {
Entity entity = Bukkit.getEntity(uuid);
return entity == null;
});
if (playerData.getRelatedEntities().isEmpty()) continue;
}
playerData.refreshCacheTime(); // Refresh cache timer if player is online
// leaving combat logic
if (playerData.getLastCombatCheckState() && !playerData.isInCombat()) {
@@ -169,7 +175,7 @@ public class PlayerManager {
}
}
if (!attackerPlayerData.isPvpEnabled()) {
if (!attackerPlayerData.isPvpEnabled() && !WorldGuardHook.isPlayerForcedToPvp(Bukkit.getPlayer(attackerId))) {
String message = switch (victimClassifier) {
case PLAYER -> plugin.getConfigCache().cannot_attack_attacker;
case PET -> plugin.getConfigCache().cannot_attack_pets_victim;
@@ -179,7 +185,7 @@ public class PlayerManager {
return new DamageCheckResult(false, attackerId, victimId, message, victimClassifier.equals(Target.EntityClassifier.PLAYER));
}
if (!victimPlayerData.isPvpEnabled()) {
if (!victimPlayerData.isPvpEnabled() && !WorldGuardHook.isPlayerForcedToPvp(Bukkit.getPlayer(victimId))) {
String message = switch (victimClassifier) {
case PLAYER -> plugin.getConfigCache().cannot_attack_victim;
case PET -> plugin.getConfigCache().cannot_attack_pets_attacker;
@@ -238,13 +244,15 @@ public class PlayerManager {
});
}
public void setPlayerPvpState(UUID uuid, boolean state) {
public CompletableFuture<Void> setPlayerPvpState(UUID uuid, boolean state) {
// If player is in cache update that
if (getPlayer(uuid) != null) {
getPlayer(uuid).setPvpEnabled(state);
}
// Update the database aswell
plugin.getSqLite().updatePlayerInfo(uuid, new PlayerData(uuid, state));
return CompletableFuture.runAsync(() -> {
// Update the database aswell
plugin.getSqLite().updatePlayerInfo(uuid, new PlayerData(uuid, state));
});
}
public CompletableFuture<Boolean> togglePlayerPvpState(UUID uuid) {
@@ -15,17 +15,19 @@ import com.sk89q.worldguard.protection.regions.RegionQuery;
import me.youhavetrouble.preventstabby.PreventStabby;
import org.bukkit.entity.Player;
import java.util.logging.Logger;
public class WorldGuardHook {
private static FlagRegistry flagRegistry;
public static StateFlag FORCE_PVP_FLAG;
public static void init() {
public static void init(Logger logger) {
try {
Class.forName("com.sk89q.worldguard.protection.flags.registry.FlagRegistry");
WorldGuardPlugin worldGuardPlugin = WorldGuardPlugin.inst();
if (WorldGuard.getInstance() == null || worldGuardPlugin == null) return;
PreventStabby.getPlugin().getLogger().info("[PreventStabby] Hooking into WorldGuard");
logger.info("Hooking into WorldGuard");
flagRegistry = WorldGuard.getInstance().getFlagRegistry();
createForcePvpFlag();
} catch (NoClassDefFoundError | ClassNotFoundException ignored) {
@@ -50,6 +52,8 @@ public class WorldGuardHook {
}
public static boolean isPlayerForcedToPvp(Player player) {
if (player == null) return false;
if (!PreventStabby.worldGuardHookEnabled()) return false;
RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery query = container.createQuery();
org.bukkit.Location loc = player.getLocation();
@@ -31,7 +31,7 @@ public class EnvironmentalListener implements Listener {
dangerousBuckets.add(Material.POWDER_SNOW_BUCKET);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onDangerousBucketDump(PlayerBucketEmptyEvent event) {
ConfigCache config = plugin.getConfigCache();
if (!config.bucket_stopper_enabled) return;
@@ -53,7 +53,7 @@ public class EnvironmentalListener implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onBlockIgnite(BlockIgniteEvent event) {
ConfigCache config = plugin.getConfigCache();
if (!config.fire_stopper_enabled) return;
@@ -77,14 +77,15 @@ public class EnvironmentalListener implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockIgnite(BlockPlaceEvent event) {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onDangerousBlockPlace(BlockPlaceEvent event) {
ConfigCache config = plugin.getConfigCache();
if (!config.block_stopper_enabled) return;
Player player = event.getPlayer();
Location location = event.getBlock().getLocation().toCenterLocation();
Target target = Target.getTarget(player);
if (target == null) return;
if (!config.getDangerousBlocks().contains(event.getBlock().getType())) return;
double radius = config.block_stopper_radius;
BoundingBox boundingBox = BoundingBox.of(location.toVector(), radius, radius, radius);
@@ -2,19 +2,20 @@ package me.youhavetrouble.preventstabby.listeners;
import me.youhavetrouble.preventstabby.PreventStabby;
import me.youhavetrouble.preventstabby.data.DamageCheckResult;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.AreaEffectCloudApplyEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.PotionSplashEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import java.util.Locale;
public class PvpListener implements Listener {
@@ -24,7 +25,7 @@ public class PvpListener implements Listener {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity attacker = event.getDamager();
Entity victim = event.getEntity();
@@ -36,7 +37,7 @@ public class PvpListener implements Listener {
plugin.getPlayerManager().handleDamageCheck(result);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPotionSplash(PotionSplashEvent event) {
if (!(event.getEntity().getShooter() instanceof Player thrower)) return;
boolean harmful = false;
@@ -56,11 +57,14 @@ public class PvpListener implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPotionCloudEffectApply(AreaEffectCloudApplyEvent event) {
if (!(event.getEntity().getSource() instanceof Player thrower)) return;
boolean harmful = false;
for (PotionEffect effect : event.getEntity().getBasePotionType().getPotionEffects()) {
AreaEffectCloud cloud = event.getEntity();
if (cloud.getBasePotionType() == null) return;
PotionType potionType = cloud.getBasePotionType();
for (PotionEffect effect : potionType.getPotionEffects()) {
if (!PotionEffectType.Category.HARMFUL.equals(effect.getType().getEffectCategory())) continue;
harmful = true;
break;
@@ -78,7 +82,7 @@ public class PvpListener implements Listener {
});
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onFish(PlayerFishEvent event) {
if (event.getCaught() instanceof Item) return;
if (plugin.getConfigCache().allow_fishing_rod_pull) return;
@@ -93,4 +97,19 @@ public class PvpListener implements Listener {
plugin.getPlayerManager().handleDamageCheck(result);
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onCommandInCombat(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
if (!plugin.getPlayerManager().getPlayer(player.getUniqueId()).isInCombat()) return;
String message = event.getMessage().toLowerCase(Locale.ROOT);
if (message.startsWith("/")) {
message = message.substring(1);
}
message = message.split(" ")[0];
if (!plugin.getConfigCache().getCombatBlockedCommands().contains(message)) return;
event.setCancelled(true);
player.sendMessage(plugin.getConfigCache().cant_do_that_during_combat);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ name: PreventStabby
version: ${project.version}
main: me.youhavetrouble.preventstabby.PreventStabby
authors: [YouHaveTrouble]
api-version: 1.20
api-version: 1.21
folia-supported: true
description: Stop people from getting stabbed!
soft-depend: