Compare commits

..

19 Commits

Author SHA1 Message Date
YouHaveTrouble dbf7595c07 remove the test suffix from version 2026-05-04 10:44:59 +02:00
YouHaveTrouble 263190daa9 possibly fix false negative check when worldguard is involved 2026-05-03 19:01:25 +02:00
YouHaveTrouble fccd413cf8 prevent null players being forwarded to events and message sending logic edge case 2025-06-19 11:39:54 +02:00
YouHaveTrouble af376437a0 bump version 2025-06-17 16:11:40 +02:00
YouHaveTrouble 636ccf516c fix broken pvp state check 2025-06-17 16:11:27 +02:00
YouHaveTrouble bd9ecc09ae do not announce combat quit when player is not in combat when quitting 2025-06-17 16:02:31 +02:00
YouHaveTrouble 9ba3bd1dc8 fix inverted config option for combat logout announce 2025-05-15 08:35:03 +02:00
YouHaveTrouble 624cc335f2 bump version 2025-05-14 17:12:30 +02:00
YouHaveTrouble 23fc50ac46 fix exception raised with no worldguard enabled 2025-05-14 17:11:40 +02:00
YouHaveTrouble 466091f1ea Merge pull request #23 from ElFrod0/master
Announce player's combat logging and then (maybe) kill afterwards
2025-05-14 17:00:19 +02:00
ElFrod0 138170c066 Comments always update 2025-05-14 02:34:39 +02:00
ElFrod0 c1d0fcf5b3 Old config migration 2025-05-14 02:33:42 +02:00
ElFrod0 b3d9ae720f Announce first then kill & adjust config 2025-05-14 02:29:00 +02:00
ElFrod0 667e441460 bump PAPI 2025-05-14 01:49:35 +02:00
YouHaveTrouble fb913d97ae 2.0.0 release 2025-05-11 12:17:52 +02:00
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
8 changed files with 175 additions and 56 deletions
+4 -4
View File
@@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.youhavetrouble</groupId>
<groupId>me.youhavetrouble.preventstabby</groupId>
<artifactId>PreventStabby</artifactId>
<version>2.0.0-pre-5</version>
<version>2.1.4</version>
<packaging>jar</packaging>
<name>PreventStabby</name>
@@ -85,7 +85,7 @@
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
@@ -104,7 +104,7 @@
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.1</version>
<version>2.11.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -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;
}
}
@@ -16,7 +16,7 @@ public class ConfigCache {
bucket_stopper_enabled,
fire_stopper_enabled,
block_stopper_enabled,
punish_for_combat_logout,
punish_for_combat_logout_kill,
punish_for_combat_logout_announce,
block_teleports_in_combat,
allow_fishing_rod_pull;
@@ -42,6 +42,10 @@ public class ConfigCache {
plugin.reloadConfig();
config = plugin.getConfig();
migrate("settings.punish_for_combat_logout.enabled",
"settings.punish_for_combat_logout.kill",
true);
// Settings
this.pvp_enabled_by_default = getBoolean(
"settings.pvp_enabled_by_default",
@@ -54,15 +58,16 @@ public class ConfigCache {
25,
List.of("How long in seconds should combat last since the last hit")
);
this.punish_for_combat_logout = getBoolean(
"settings.punish_for_combat_logout.enabled",
this.punish_for_combat_logout_kill = getBoolean(
"settings.punish_for_combat_logout.kill",
true,
List.of("Should players be killed if they log out during combat?")
);
this.punish_for_combat_logout_announce = getBoolean(
"settings.punish_for_combat_logout.announce",
true,
List.of("Should killing of a player that logged out of combat be announced?")
List.of("Should we announce that player logged out of combat?")
);
List<String> commandsBlockedInCombat = getList(
@@ -199,43 +204,74 @@ public class ConfigCache {
}
private String getString(String path, @NotNull String def, @Nullable List<String> comments) {
if (config.isSet(path)) return config.getString(path, def);
String value;
if (config.isSet(path)) {
value = config.getString(path, def);
} else {
config.set(path, def);
value = def;
}
if (comments != null) config.setComments(path, comments);
return def;
return value;
}
private boolean getBoolean(String path, boolean def, @Nullable List<String> comments) {
if (config.isSet(path)) return config.getBoolean(path, def);
boolean value;
if (config.isSet(path)) {
value = config.getBoolean(path, def);
} else {
config.set(path, def);
value = def;
}
if (comments != null) config.setComments(path, comments);
return def;
return value;
}
private double getDouble(String path, double def, @Nullable List<String> comments) {
if (config.isSet(path)) return config.getDouble(path, def);
double value;
if (config.isSet(path)) {
value = config.getDouble(path, def);
} else {
config.set(path, def);
value = def;
}
if (comments != null) config.setComments(path, comments);
return def;
return value;
}
private long getLong(String path, long def, @Nullable List<String> comments) {
if (config.isSet(path)) return config.getLong(path, def);
long value;
if (config.isSet(path)) {
value = config.getLong(path, def);
} else {
config.set(path, def);
value = def;
}
if (comments != null) config.setComments(path, comments);
return def;
return value;
}
private List<String> getList(String path, List<String> def, @Nullable List<String> comments) {
if (config.isSet(path)) return config.getStringList(path);
List<String> value;
if (config.isSet(path)) {
value = config.getStringList(path);
} else {
config.set(path, def);
value = def;
}
if (comments != null) config.setComments(path, comments);
return def;
return value;
}
private List<String> getList(String path, List<String> def) {
return getList(path, def, null);
}
private void migrate(String oldPath, String newPath, @Nullable Object defaultValue) {
if (config.isSet(oldPath) && !config.isSet(newPath)) {
Object value = config.get(oldPath);
config.set(newPath, value != null ? value : defaultValue);
config.set(oldPath, 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);
}
}
@@ -44,14 +44,15 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerLeave(PlayerQuitEvent event) {
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;
if (PreventStabby.getPlugin().getConfigCache().punish_for_combat_logout_announce) {
PluginMessages.broadcastMessage(player, PreventStabby.getPlugin().getConfigCache().punish_for_combat_logout_message);
}
if (!PreventStabby.getPlugin().getConfigCache().punish_for_combat_logout_kill) return;
player.setHealth(0);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onPlayerTeleport(PlayerTeleportEvent event) {
@@ -78,7 +79,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,26 +31,31 @@ 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;
playerData.refreshCacheTime(); // Refresh cache timer if player is online
playerData.getRelatedEntities().removeIf( uuid -> {
Entity entity = Bukkit.getEntity(uuid);
return entity == null;
});
if (player == null || !player.isOnline()) {
// player not online, so check for related entities
if (playerData.getRelatedEntities().isEmpty()) continue;
}
playerData.refreshCacheTime();
if (player == null || !player.isOnline()) continue; // If player is offline, skip the rest of the logic
// leaving combat logic
if (playerData.getLastCombatCheckState() && !playerData.isInCombat()) {
PlayerLeaveCombatEvent leaveCombatEvent = null;
@@ -169,7 +176,14 @@ public class PlayerManager {
}
}
if (!attackerPlayerData.isPvpEnabled()) {
Boolean attackerForcedPvpState = null;
Boolean victimForcedPvpState = null;
if (PreventStabby.worldGuardHookEnabled()) {
attackerForcedPvpState = WorldGuardHook.isPlayerForcedToPvp(Bukkit.getPlayer(attackerId));
victimForcedPvpState = WorldGuardHook.isPlayerForcedToPvp(Bukkit.getPlayer(victimId));
}
if (!attackerPlayerData.isPvpEnabled() || Boolean.FALSE.equals(attackerForcedPvpState)) {
String message = switch (victimClassifier) {
case PLAYER -> plugin.getConfigCache().cannot_attack_attacker;
case PET -> plugin.getConfigCache().cannot_attack_pets_victim;
@@ -179,7 +193,7 @@ public class PlayerManager {
return new DamageCheckResult(false, attackerId, victimId, message, victimClassifier.equals(Target.EntityClassifier.PLAYER));
}
if (!victimPlayerData.isPvpEnabled()) {
if (!victimPlayerData.isPvpEnabled() || Boolean.FALSE.equals(victimForcedPvpState)) {
String message = switch (victimClassifier) {
case PLAYER -> plugin.getConfigCache().cannot_attack_victim;
case PET -> plugin.getConfigCache().cannot_attack_pets_attacker;
@@ -238,13 +252,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);
}
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) {
@@ -49,14 +51,20 @@ public class WorldGuardHook {
}
}
public static boolean isPlayerForcedToPvp(Player player) {
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();
if (loc.getWorld() == null) return false;
LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player);
ApplicableRegionSet set = query.getApplicableRegions(new Location(BukkitAdapter.adapt(loc.getWorld()), loc.getX(), loc.getY(), loc.getZ()));
return set.testState(localPlayer, FORCE_PVP_FLAG);
return switch (set.queryState(localPlayer, FORCE_PVP_FLAG)) {
case ALLOW -> true;
case DENY -> false;
case null-> null;
};
}
}
@@ -9,6 +9,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -33,12 +34,12 @@ public class PluginMessages {
return PreventStabby.getPlugin().getServer().getPluginManager().getPlugin("PlaceholderAPI") != null;
}
public static void sendMessage(CommandSender sender, String message) {
public static void sendMessage(@NotNull CommandSender sender, String message) {
if ("".equals(message)) return;
sender.sendMessage(parseMessage(sender, message));
}
public static void sendActionBar(Player player, String message) {
public static void sendActionBar(@NotNull Player player, String message) {
if ("".equals(message)) return;
Component parsedMessage = parseMessage(player, message);
player.sendActionBar(parsedMessage);